世奇笔记

前言

关于博客

这个博客

搭建教程

搭建这个博客的时候,我使用了如下工具:

  • Markdown:书写文档

  • Pandoc:格式转化工具

  • Sphinx:中文检索及生成静态网页

  • Github:项目托管

  • ReadtheDocs:网页发布

1. 安装Sphnix

  • 安装

可参照Sphnix官网文档

  • 初始化

创建一个工程目录,执行 sphinx-quickstart 命令

sphinx-quickstart
  • 执行完成会看到如下文件

.
├── build
├── make.bat
├── Makefile
└── source
    ├── conf.py
    └──index.rst

build:这是触发特定输出后用来存放所生成的文件的目录。

make.bat:bat脚本

Makefile:使用 make 命令,可以构建文档输出。

source:这是存放.rst文件的目录。

conf.py::raw-latex:`用于存放 `Sphinx 的配置值

index.rst:文档项目的 root 目录

2.配置及拓展

对Sphinx做了如下拓展:

  • 配置主题

  • 支持LaTex

  • 支持中文检索

配置文件及拓展文件,都可以在Github上找到

3.编写文章及生成静态html

Sphinx默认支持rst文件。在source目录下,新增preface.rst

内容如下:

前言
====

关于博客
--------

这个博客

搭建教程
--------

搭建这个博客的时候,我使用了如下工具:

-  Markdown:书写文档
-  Pandoc:格式转化工具
-  Sphinx:中文检索及生成静态网页
-  Github:项目托管
-  ReadtheDocs:网页发布

然后写进排版配置文件source/index.rst

Python学习笔记
==========================================

.. toctree::
   :maxdepth: 2
   :glob:

   preface

执行make html生成html静态文件

$ make html
Running Sphinx v3.2.1
loading translations [zh_CN]... done
making output directory... done
WARNING: multiple files found for the document "aboutme": ['aboutme.rst', 'aboutme.md']
Use '/Users/bjhl/PycharmProjects/mkdocs/source/aboutme.rst' for the build.
WARNING: multiple files found for the document "preface": ['preface.md', 'preface.rst']
Use '/Users/bjhl/PycharmProjects/mkdocs/source/preface.rst' for the build.
building [mo]: targets for 0 po files that are out of date
building [html]: targets for 9 source files that are out of date
updating environment: [new config] 9 added, 0 changed, 0 removed
reading sources... [100%] preface
build succeeded.
The HTML pages are in build/html.

在build文件夹下可以找到index.html文件,用浏览器打开,可以看到效果

image-20200904174442779

image-20200904174442779

因为我习惯使用MarkDown写文章,可以使用Pandoc(官方文档)这个工具转换文档格式。转换命令如下:

pandoc -V mainfont="SimSun" -f markdown -t rst hello.md -o hello.rst

4.托管项目

我把项目托管在GitHub上,具体方法就不介绍了,.gitignore内容如下:

build/
.idea/
*.pyc

5.发布上线

代码托管后,我使用Read the Docs来发布。

首先在Read the Docs注册账号,关联GitHub。

导入代码库,填好对应的信息,构建网页后,可以看到发布后的在线地址。

image-20200904181900385

image-20200904181900385

第一章:Python基础

1.1 定时任务APScheduler

1.简介

APScheduler是基于Quartz的一个Python定时任务框架。提供了基于日期、固定时间间隔以及crontab类型的任务,并且可以持久化任务。

官方文档

2.安装

$ pip install apscheduler

3.基本概念

APScheduler有四大组件:

触发器 triggers 触发器包含调度逻辑。每个作业都有自己的触发器,用于确定下一个任务何时运行。除了初始配置之外,触发器是完全无状态的。

有三种内建的trigger:

  • date: 特定的时间点触发

  • interval: 固定时间间隔触发

  • cron: 在特定时间周期性地触发

任务储存器 job stores

用于存放任务,把任务存放在内存(默认MemoryJobStore)或数据库中。

执行器 executors

执行器是将任务提交到线程池或进程池中运行,当任务完成时,执行器通知调度器触发相应的事件。

调度器 schedulers

把上方三个组件作为参数,通过创建调度器实例来运行

根据开发需求选择相应的组件,下面是不同的调度器组件:

  • BlockingScheduler 阻塞式调度器:适用于只跑调度器的程序。

  • BackgroundScheduler 后台调度器:适用于非阻塞的情况,调度器会在后台独立运行。

  • AsyncIOScheduler AsyncIO调度器,适用于应用使用AsnycIO的情况。

  • GeventScheduler Gevent调度器,适用于应用通过Gevent的情况。

  • TornadoScheduler Tornado调度器,适用于构建Tornado应用。

  • TwistedScheduler Twisted调度器,适用于构建Twisted应用。

  • QtScheduler Qt调度器,适用于构建Qt应用。

4.使用步骤

新建一个调度器schedulers

添加调度任务

运行调度任务

5.示例

5.1 触发器date

特定的时间点触发,只执行一次。参数如下:

参数

说明

run_date (datetime 或 str)

作业的运行日期或时间

timezone (datetime.tzinfo 或 str)

指定时区

使用例子:

from datetime import datetime
from datetime import date
from apscheduler.schedulers.blocking import BlockingScheduler
def job(text):
    print(text)
scheduler = BlockingScheduler()
# 在 2019-8-30 运行一次 job 方法
scheduler.add_job(job, 'date', run_date=date(2019, 8, 30), args=['text1'])
# 在 2019-8-30 01:00:00 运行一次 job 方法
scheduler.add_job(job, 'date', run_date=datetime(2019, 8, 30, 1, 0, 0), args=['text2'])
# 在 2019-8-30 01:00:01 运行一次 job 方法
scheduler.add_job(job, 'date', run_date='2019-8-30 01:00:00', args=['text3'])
scheduler.start()

5.2 触发器interval

固定时间间隔触发。参数如下:

参数

说明

weeks (int)

间隔几周

days (int)

间隔几天

hours (int)

间隔几小时

minutes (int)

间隔几分钟

seconds (int)

间隔多少秒

start_date (datetime 或 str)

开始日期

end_date (datetime 或 str)

结束日期

timezone (datetime.tzinfo 或str)

指定时区

使用例子:

import time
from apscheduler.schedulers.blocking import BlockingScheduler
def job(text):
    t = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
    print('{} --- {}'.format(text, t))
scheduler = BlockingScheduler()
# 每隔 1分钟 运行一次 job 方法
scheduler.add_job(job, 'interval', minutes=1, args=['job1'])
# 在 2019-08-29 22:15:00至2019-08-29 22:17:00期间,每隔1分30秒 运行一次 job 方法
scheduler.add_job(job, 'interval', minutes=1, seconds=30, start_date='2019-08-29 22:15:00', end_date='2019-08-29 22:17:00', args=['job2'])
scheduler.start()

5.3 触发器cron

在特定时间周期性地触发。参数如下:

参数

说明

year (int 或 str)

年,4位数字

month (int 或 str)

月 (范围1-12)

day (int 或 str)

日 (范围1-31)

week (int 或 str)

周 (范围1-53)

day_of_week (int 或 str)

周内第几天或者星期几 (范围0-6 或者 mon,tue,wed,thu,fri,sat,sun)

hour (int 或 str)

时 (范围0-23)

minute (int 或 str)

分 (范围0-59)

second (int 或 str)

秒 (范围0-59)

start_date (datetime 或 str)

最早开始日期(包含)

end_date (datetime 或 str)

最晚结束时间(包含)

timezone (datetime.tzinfo 或str)

指定时区

这些参数支持算数表达式,取值格式有如下:

Express ion

**Fi eld* *

Description

*

any

Fire on every value

*/a

any

Fire every a values, starting from the minimum

a-b

any

Fire on any value within the a-b range (a must be smaller than b)

a-b/c

any

Fire every c values within the a-b range

xth y

day

Fire on the x -th occurrence of weekday y within the month

last x

day

Fire on the last occurrence of weekday x within the month

last

day

Fire on the last day within the month

x,y,z

any

Fire on any matching expression; can combine any number of any of the above expressio

使用例子:

import time
from apscheduler.schedulers.blocking import BlockingScheduler
def job(text):
    t = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
    print('{} --- {}'.format(text, t))
scheduler = BlockingScheduler()
# 在每天22点,每隔 1分钟 运行一次 job 方法
scheduler.add_job(job, 'cron', hour=22, minute='*/1', args=['job1'])
# 在每天22和23点的25分,运行一次 job 方法
scheduler.add_job(job, 'cron', hour='22-23', minute='25', args=['job2'])
scheduler.start()

5.4 通过装饰器scheduled_job()添加方法

添加任务的方法有两种:

(1)通过调用add_job()

(2)通过装饰器scheduled_job():

第一种方法是最常用的方法。第二种方法主要是方便地声明在应用程序运行时不会更改的任务。该 add_job()方法返回一个apscheduler.job.Job实例,可以使用该实例稍后修改或删除该任务。

使用例子:

import time
from apscheduler.schedulers.blocking import BlockingScheduler
scheduler = BlockingScheduler()
@scheduler.scheduled_job('interval', seconds=5)
def job1():
    t = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
    print('job1 --- {}'.format(t))
@scheduler.scheduled_job('cron', second='*/7')
def job2():
    t = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
    print('job2 --- {}'.format(t))
scheduler.start()

6.配置调度程序

生成一个名为“default”的RedisJobStore和名为“default”的ThreadPoolExecutor的BackgroundScheduler,默认最大线程数为10

default_redis_jobstore = RedisJobStore(
        db=2,
        jobs_key="apschedulers.default_jobs",
        run_times_key="apschedulers.default_run_times",
        host="",
        port=6379,
        password="xxx"
    )

    executor = ThreadPoolExecutor(10)
    init_scheduler_options = {
        "jobstores": {
            "default": default_redis_jobstore
        },
        "executors": {
            "default": executor
        },
        "job_defaults": {
            'misfire_grace_time': 20 * 60,  # 最大错过时间
            'coalesce': False,  # 是否合并执行
            'max_instances': 20  # 最大实例数
        }
    }
    sched = BackgroundScheduler(**init_scheduler_options)
    sched.start()

7.控制调度程序

7.1 启动调度程序

调用start()方法

scheduler.start()

7.2 新增工作

  • 调用方法add_job()

  • 通过装饰器scheduled_job()

7.3 删除工作**

  • 通过调用remove_job(),使用作业ID或别名删除工作

scheduler.add_job(myfunc, 'interval', minutes=2, id='my_job_id')
scheduler.remove_job('my_job_id')
  • 通过调用Job实例的remove()方法

job = scheduler.add_job(myfunc, 'interval', minutes=2)
job.remove()

7.4 暂停和恢复工作

scheduler.pause() # 暂停
scheduler.resume() # 恢复

7.5 修改工作

scheduler.reschedule_job('my_job_id', trigger='cron', minute='*/5')

7.6 获取计划的作业列表

scheduler.get_jobs()    # 作业列表
scheduler.get_job('job_id') # 指定id的作业

7.7 关闭调度程序

scheduler.shutdown()

7.8 首次执行时间问题

scheduler.add_job(job1, 'interval', seconds=5, max_instances=1, next_run_time=datetime.datetime.now())

1.2 重试模块retrying

1.简介

一个很好用的关于重试的Python包,可以用来自动重试一些可能会运行失败的程序段。

2.安装

$ pip install retrying

3.API介绍

def __init__(self,
                 stop=None, wait=None,
                 stop_max_attempt_number=None,
                 stop_max_delay=None,
                 wait_fixed=None,
                 wait_random_min=None, wait_random_max=None,
                 wait_incrementing_start=None, wait_incrementing_increment=None,
                 wait_exponential_multiplier=None, wait_exponential_max=None,
                 retry_on_exception=None,
                 retry_on_result=None,
                 wrap_exception=False,
                 stop_func=None,
                 wait_func=None,
                 wait_jitter_max=None)
  • stop_max_attempt_number:用来设定最大的尝试次数,超过该次数就停止重试

  • stop_max_delay:比如设置成10000,那么从被装饰的函数开始执行的时间点开始,到函数成功运行结束或者失败报错中止的时间点,只要这段时间超过10秒,函数就不会再执行了

  • wait_fixed:设置在两次retrying之间的停留时间

  • wait_random_min和wait_random_max:用随机的方式产生两次retrying之间的停留时间

  • wait_exponential_multiplier和wait_exponential_max:以指数的形式产生两次retrying之间的停留时间,产生的值为2^previous_attempt_number * wait_exponential_multiplier,previous_attempt_number是前面已经retry的次数,如果产生的这个值超过了wait_exponential_max的大小,那么之后两个retrying之间的停留值都为wait_exponential_max

  • 我们可以指定要在出现哪些异常的时候再去retry,这个要用retry_on_exception传入一个函数对象

4.示例

  • @retry装饰器,如出现异常会一直重试

@retry
def never_give_up_never_surrender():
    print "Retry forever ignoring Exceptions, don't wait between retries"
  • stop_max_attempt_number 设置最大重试次数

@retry(stop_max_attempt_number=7)
def stop_after_7_attempts():
    print "Stopping after 7 attempts"
    raise
  • stop_max_delay 设置失败重试的最大时间, 单位毫秒,超出时间,则停止重试

@retry(stop_max_delay=10000)
def stop_after_10_s():
    print "Stopping after 10 seconds"
    raise
  • wait_fixed 设置失败重试的间隔时间

@retry(wait_fixed=2000, stop_max_delay=10000)
def wait_2_s():
    print "Wait 2 second between retries"
    raise
  • wait_random_min, wait_random_max 设置失败重试随机性间隔时间

@retry(wait_random_min=1000, wait_random_max=5000, stop_max_delay=10000)
def wait_random_1_to_5_s():
    print "Randomly wait 1 to 5 seconds between retries"
    raise
  • wait_exponential_multiplier-间隔时间倍数增加,wait_exponential_max-最大间隔时间

import time

@retry(wait_exponential_multiplier=1000, wait_exponential_max=10000)
def wait_exponential_1000():
    print "Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards"
    print int(time.time())
    raise
  • retry_on_exception指定异常类型,指定的异常类型会重试,不指定的类型,会直接异常退出,wrap_exception参数设置为True,则其他类型异常,或包裹在RetryError中,会看到RetryError和程序抛的Exception error

def retry_if_io_error(exception):
    """Return True if we should retry (in this case when it's an IOError), False otherwise"""
    return isinstance(exception, IOError)

@retry(retry_on_exception=retry_if_io_error)
def might_io_error():
    print "Retry forever with no wait if an IOError occurs, raise any other errors"
    raise Exception('a')


@retry(retry_on_exception=retry_if_io_error, wrap_exception=True)
def only_raise_retry_error_when_not_io_error():
    print "Retry forever with no wait if an IOError occurs, raise any other errors wrapped in RetryError"
    raise Exception('a')
  • retry_on_result, 指定要在得到哪些结果的时候去retry,retry_on_result传入一个函数对象,在执行get_result成功后,会将函数的返回值通过形参result的形式传入retry_if_result_none函数中,如果返回值是None那么就进行retry,否则就结束并返回函数值

def retry_if_result_none(result):
    return result is None

@retry(retry_on_result=retry_if_result_none)
def get_result():
    print 'Retry forever ignoring Exceptions with no wait if return value is None'
    return None

1.3 制作及读取QR码

1.相关源码

制作 QR 码时所用到的第三库是 qrcode 。读取时所用第三方库是 zxing 和 pyzbar 。

制作 QR 码源码

import qrcode

# 二维码内容
data = "test"
# 生成二维码
img = qrcode.make(data=data)
# 保存二维码为文件
img.save("test.png")

利用 pyzbar 读取 QR 码源码

# -*- coding: utf-8 -*-
"""pyzbar 识别 QR 码"""

import os
from PIL import Image
from pyzbar import pyzbar


def decode_qr_code(code_img_path):

    if not os.path.exists(code_img_path):
        raise FileExistsError(code_img_path)

    return pyzbar.decode(Image.open(code_img_path))[0].data.decode()


if __name__ == '__main__':
    img = "test.png"
    print(decode_qr_code(img))

利用 zxing 读取 QR 码源码

# -*- coding: utf-8 -*-
"""使用 zxing 读取二维码"""

import zxing

reader = zxing.BarCodeReader()
barcode = reader.decode("test.png")
text = barcode.parsed

print(text)

2.问题及解决方法

2.1 使用 pyzbar 读取时报以下错误

$ ImportError: Unable to find zbar shared library

解决方法:安装 zbar-tools

$ sudo apt-get install zbar-tools

2.2 使用 zxing 读取时报以下错误

$ zxing No such file or directory: 'java'

解决方法:安装 jpype 扩展

$ sudo apt-get install python-jpype

cdcd

第二章:高级编程

第三章:异步IO

第四章:Web开发

第五章:数据分析

5.1 Numpy

NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。

Numpy的优势

  • Numpy执行数值计算任务要比python代码便捷

  • Numpy中的数组存储效率和输入输出的性能远优于Python中等价的数据结构,且能够提升的性能是与数组中的元素成比例的

  • Numpy中的大部分代码是C编写的,具有优异的性能

Numpy的array与Python原生list运行效率对比

import numpy as np
import time
import random

a = []
for i in range(100000000):
    a.append(i)
t = time.time()
sum(a)
print(time.time()-t)

b = np.array(a)
t = time.time()
np.sum(b)
print(time.time()-t)

0.5847101211547852
0.09261441230773926

1. Numpy的Ndrray对象

Numpy最重要的一个特点是N维数组对象ndarry,它是一系列同类型数据的集合,以下标0为开始进行集合中元素的索引。

1.1 创建一维数组
import numpy as np
# 1. 直接传入列表
t1 = np.array([1,2,3,4])

# 2. 传入range生成序列
t2 = np.array(range(5))

# 3. 使用numpy自带的np.array()生成数组
t3 = np.arange(0, 10, 2)
1.2 创建二维数组
import numpy as np
list_ = [[1,2],[3,4],[5,6]]
print(np.array(list_))

[[1 2]
 [3 4]
 [5 6]]
1.3 常用属性
import numpy as np
list_ = [[1,2],[3,4],[5,6]]
A = np.array(list_)

# 数组的维度
A.ndim

# 数组形状
A.shape

# 数组有多少元素
A.size

属性

说明

ndarray.ndim

秩,即轴的数量或维度的数量

ndarray.shape

数组的维度,对于矩阵,n 行 m 列

ndarray.size

数组元素的总个数,相当于 .shape 中 n*m 的值

ndarray.dtype

ndarray 对象的元素类型

ndarray.itemsize

ndarray 对象中每个元素的大小,以字节为单位

ndarray.flags

ndarray 对象的内存信息

ndarray.real

ndarray元素的实部

ndarray.imag

ndarray 元素的虚部

ndarray.data

包含实际数组元素的缓冲区,由于一般通过数组的索引获取元素,所以通常不需要使用这个属性。

1.4 调整数组形状
four = np.array([[1,2,3],[4,5,6]])
# 修改的是原有的
four.shape = (3,2)
print(four)

# 返回一个新的数组
four = four.reshape(3,2)
print(four)

# 将多维变成一维数组
five = four.reshape((6,),order='F')
# 默认情况下‘C’以行为主的顺序展开,‘F’(Fortran风格)意味着以列的顺序展开
six = four.flatten(order='F')
print(five)
print(six)

# 拓展:数组的形状
t = np.arange(24)
print(t)
print(t.shape)

# 转换成二维
t1 = t.reshape((4,6))
print(t1)
print(t1.shape)

# 转成三维
t2 = t.reshape((2,3,4))
print(t2)
print(t2.shape)
1.5 数组转list
# 将数组转成list
a= np.array([9, 12, 88, 14, 25])
list_a = a.tolist()
print(list_a)
print(type(list_a))

2. Numpy的数据类型

f = np.array([1,2,3,4,5], dtype = np.int16)
# 返回数组中每个元素的字节单位长度
print(f.itemsize) # 1 np.int8(一个字节)

# 获取数据类型
print(f.dtype)

# 调整数据类型
f1 = f.astype(np.int64)
print(f1.dtype)

# 拓展随机生成小数
# 使用python语法,保留两位
print(round(random.random(),2))
arr = np.array([random.random() for i in range(10)])
# 取小数点后两位
print(np.round(arr,2))

Numpy的基本类型

名称

描述

bool_

布尔型数据类型(True 或者 False)

int_

默认的整数类型(类似于 C 语言中的 long,int32 或 int64)

intc

与 C 的 int 类型一样,一般是 int32 或 int 64

intp

用于索引的整数类型(类似于 C 的 ssize_t,一般情况下仍然是 int32 或 int64)

int8

字节(-128 to 127)

int16

整数(-32768 to 32767)

int32

整数(-2147483648 to 2147483647)

int64

整数(-9223372036854775808 to 9223372036854775807)

uint8

无符号整数(0 to 255)

uint16

无符号整数(0 to 65535)

uint32

无符号整数(0 to 4294967295)

uint64

无符号整数(0 to 18446744073709551615)

float_

float64 类型的简写

float16

半精度浮点数,包括:1 个符号位,5 个指数位,10 个尾数位

float32

单精度浮点数,包括:1 个符号位,8 个指数位,23 个尾数位

float64

双精度浮点数,包括:1 个符号位,11 个指数位,52 个尾数位

complex_

complex128 类型的简写,即 128 位复数

complex64

复数,表示双 32 位浮点数(实数部分和虚数部分)

complex128

复数,表示双 64 位浮点数(实数部分和虚数部分)

3. 广播

广播(Broadcast)是 Numpy 对不同形状(shape)的数组进行数值计算的方式, 对数组的算术运算通常在相应的元素上进行。

如果两个数组 a 和 b 形状相同,即满足 a.shape == b.shape,那么 a*b 的结果就是 a 与 b 数组对应位相乘。这要求维数相同,且各维度的长度相同。

相同形状的数组进行计算

t1 = np.arange(24).reshape((6,4))
t2 = np.arange(24).reshape((6,4))
print(t1)
print(t2)
print(t1+t2)
print(t1*t2)

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]
 [20 21 22 23]]

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]
 [20 21 22 23]]

[[ 0  2  4  6]
 [ 8 10 12 14]
 [16 18 20 22]
 [24 26 28 30]
 [32 34 36 38]
 [40 42 44 46]]

[[  0   1   4   9]
 [ 16  25  36  49]
 [ 64  81 100 121]
 [144 169 196 225]
 [256 289 324 361]
 [400 441 484 529]]

与列数相同的一维数组进行计算

t1 = np.arange(24).reshape((4,6))
t2 = np.arange(0,6)
print(t1)
print(t2)
print(t1-t2)

[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]

[0 1 2 3 4 5]

[[ 0  0  0  0  0  0]
 [ 6  6  6  6  6  6]
 [12 12 12 12 12 12]
 [18 18 18 18 18 18]]

与行数相同的以为数组进行计算

t1 = np.arange(24).reshape((4,6))
t2 = np.arange(4).reshape((4,1))
print(t1)
print(t2)
print(t1-t2)

[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]

[[0]
 [1]
 [2]
 [3]]

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

4. 索引和切片

一维数组操作

import numpy as np
a = np.arange(10)
# 冒号分隔切片参数 start:stop:step 来进行切片操作
print(a[2:7:2])    # 从索引 2 开始到索引 7 停止,间隔为 2

# 如果只放置一个参数,如 [2],将返回与该索引相对应的单个元素
print(a[2],a)

# 如果为 [2:],表示从该索引开始以后的所有项都将被提取
print(a[2:])

[2 4 6]
2 [0 1 2 3 4 5 6 7 8 9]
[2 3 4 5 6 7 8 9]

多维数组操作

import numpy as np
t1 = np.arange(24).reshape(4,6)
print(t1)

print('*'*20)

print(t1[1]) # 取一行(一行代表是一条数据,索引也是从0开始的)

print(t1[1,:]) # 取一行

print(t1[1:])# 取连续的多行

print(t1[1:3,:])# 取连续的多行

print(t1[[0,2,3]])# 取不连续的多行

print(t1[[0,2,3],:])# 取不连续的多行

print(t1[:,1])# 取一列

print(t1[:,1:])# 连续的多列

print(t1[:,[0,2,3]])# 取不连续的多列

print(t1[2,3])# # 取某一个值,三行四列

print(t1[[0,1,1],[0,1,3]])# 取多个不连续的值,[[行,行。。。],[列,列。。。]]


[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]

********************

[ 6  7  8  9 10 11]

[ 6  7  8  9 10 11]

[[ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]

[[ 6  7  8  9 10 11]
 [12 13 14 15 16 17]]

[[ 0  1  2  3  4  5]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]

[[ 0  1  2  3  4  5]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]

[ 1  7 13 19]

[[ 1  2  3  4  5]
 [ 7  8  9 10 11]
 [13 14 15 16 17]
 [19 20 21 22 23]]

[[ 0  2  3]
 [ 6  8  9]
 [12 14 15]
 [18 20 21]]

15

[0 7 9]

5. 修改数组中的数值

import numpy as np
t = np.arange(24).reshape(4,6)
# 修改某一行的值
t[1,:]=0
# 修改某一列的值
t[:,1]=0
# 修改连续多行
t[1:3,:]=0
# 修改连续多列
t[:,1:4]=0
# 修改多行多列,取第二行到第四行,第三列到第五列
t[1:4,2:5]=0
# 修改多个不相邻的点
t[[0,1],[0,3]]=0
# 可以根据条件修改,比如讲小于10的值改掉
t[t<10]=0
# 使用逻辑判断
# np.logical_and &
# np.logical_or |
# np.logical_not ~
t[(t>2)&(t<6)]=0 # 与
t[(t<2)|(t>6)]=0 # 或
t[~(t>6)]=0 # 非
print(t)
# 拓展
# 三目运算( np.where(condition, x, y)满足条件(condition),输出x,不满足输出y。))
# score = np.array([[80,88],[82,81],[75,81]])
# result = np.where(score>80,True,False)
# print(result)

6. 数组的添加、删除和去重

6.1 append

append 函数在数组的末尾添加值。 追加操作会分配整个数组,并把原来的数组复制到新数组

参数说明:

  • arr:输入数组

  • values:要向arr添加的值,需要和arr形状相同(除了要添加的轴)

  • axis:默认为 None。当axis无定义时,是横向加成,返回总是为一维数组!当axis有定义的时候,分别为0和1的时候。当 axis有定义的时候,分别为0和1的时候(列数要相同)。当axis为1时,数组是加在右边(行数要相同)

import numpy as np
a = np.array([[1,2,3],[4,5,6]])
print ('第一个数组:')
print (a)
print ('\n')
print ('向数组添加元素:')
print (np.append(a, [7,8,9]))
print ('\n')
print ('沿轴 0 添加元素:')
print (np.append(a, [[7,8,9]],axis = 0))
print ('\n')
print ('沿轴 1 添加元素:')
print (np.append(a, [[5,5,5],[7,8,9]],axis = 1))

执行结果

第一个数组:
[[1 2 3]
 [4 5 6]]


向数组添加元素:
[1 2 3 4 5 6 7 8 9]


沿轴 0 添加元素:
[[1 2 3]
 [4 5 6]
 [7 8 9]]


沿轴 1 添加元素:
[[1 2 3 5 5 5]
 [4 5 6 7 8 9]]
6.2 insert

insert函数在给定索引之前,沿给定轴在输入数组中插入值。

import numpy as np
a = np.array([[1,2],[3,4],[5,6]])
print ('第一个数组:')
print (a)
print ('\n')
print ('未传递 Axis 参数。 在插入之前输入数组会被展开。')
print (np.insert(a,3,[11,12]))
print ('\n')
print ('传递了 Axis 参数。 会广播值数组来配输入数组。')
print ('沿轴 0 广播:')
print (np.insert(a,1,[11],axis = 0))
print ('\n')
print ('沿轴 1 广播:')
print (np.insert(a,1,11,axis = 1))

执行结果

第一个数组:
[[1 2]
 [3 4]
 [5 6]]


未传递 Axis 参数。 在插入之前输入数组会被展开。
[ 1  2  3 11 12  4  5  6]


传递了 Axis 参数。 会广播值数组来配输入数组。
沿轴 0 广播:
[[ 1  2]
 [11 11]
 [ 3  4]
 [ 5  6]]


沿轴 1 广播:
[[ 1 11  2]
 [ 3 11  4]
 [ 5 11  6]]
6.3 delete

delete函数返回从输入数组中删除指定子数组的新数组。与 insert() 函数的情况一样,如果未提供轴参数, 则输入数组将展开。

参数说明

  • arr:输入数组

  • obj:可以被切片,整数或者整数数组,表明要从输入数组删除的子数组

  • axis:沿着它删除给定子数组的轴,如果未提供,则输入数组会被展开

a = np.arange(12).reshape(3,4)
print ('第一个数组:')
print (a)
print ('\n')
print ('未传递 Axis 参数。 在删除之前输入数组会被展开。')
print (np.delete(a,5))
print ('\n')
print ('删除每一行中的第二列:')
print (np.delete(a,1,axis = 1))
print ('\n')

执行结果

第一个数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]


未传递 Axis 参数。 在删除之前输入数组会被展开。
[ 0  1  2  3  4  6  7  8  9 10 11]


删除每一行中的第二列:
[[ 0  2  3]
 [ 4  6  7]
 [ 8 10 11]]
6.4 unique

unique 函数用于去除数组中的重复元素。

参数说明:

  • arr:输入数组,如果不是一维数组则会展开

  • return_index:如果为true,返回新列表元素在旧列表中的位置(下标),并以列表形式储

  • return_inverse:如果为true,返回旧列表元素在新列表中的位置(下标),并以列表形式储

  • return_counts:如果为true,返回去重数组中的元素在原数组中的出现次数

a = np.array([5,2,6,2,7,5,6,8,2,9])
print ('第一个数组:')
print (a)
print ('\n')
print ('第一个数组的去重值:')
u = np.unique(a)
print (u)
print ('\n')
print ('去重数组的索引数组:')
u,indices = np.unique(a, return_index = True)
print (indices)
print ('\n')
print ('我们可以看到每个和原数组下标对应的数值:')
print (a)
print ('\n')
print ('去重数组的下标:')
u,indices = np.unique(a,return_inverse = True)
print (u)
print (indices)
print ('\n')
print ('返回去重元素的重复数量:')
u,indices = np.unique(a,return_counts = True)
print (u)
print (indices)

执行结果

第一个数组:
[5 2 6 2 7 5 6 8 2 9]


第一个数组的去重值:
[2 5 6 7 8 9]


去重数组的索引数组:
[1 0 2 4 7 9]


我们可以看到每个和原数组下标对应的数值:
[5 2 6 2 7 5 6 8 2 9]


去重数组的下标:
[2 5 6 7 8 9]
[1 0 2 0 3 1 2 4 0 5]


返回去重元素的重复数量:
[2 5 6 7 8 9]
[3 2 2 1 1 1]

7. 排序和条件筛选

7.1 排序

NumPy 提供了多种排序的方法。 这些排序函数实现不同的排序算法,每个排序算法的特征在于执行速度,最坏情况性能,所需的工作空间和算法的稳定性。 下表显示了三种排序算法的比较。

种类

速度

最坏情况

工作空间

稳定性

'quicksort'(快速排序)

1

O(n^2)

0

'mergesort'(归并排序)

2

O(n*log(n))

~n/2

'heapsort'(堆排序)

3

O(n*log(n))

0

  • numpy.sort()

numpy.sort() 函数返回输入数组的排序副本。函数格式如下:

numpy.sort(a, axis, kind, order)

参数说明:

  • a: 要排序的数组

  • axis: 沿着它排序数组的轴,如果没有数组会被展开,沿着最后的轴排序, axis=0 按列排序,axis=1 按行排序

  • kind: 默认为’quicksort’(快速排序)

  • order: 如果数组包含字段,则是要排序的字段

  • numpy.argsort()

numpy.argsort() 函数返回的是数组值从小到大的索引值。

  • numpy.lexsort()

numpy.lexsort() 用于对多个序列进行排序。把它想象成对电子表格进行排序,每一列代表一个序列,排序时优先照顾靠后的列。

  • msort、sort_complex、partition、argpartition

函数

描述

msort(a)

数组按第一个轴排序,返回排序后的数组副本。np.msort(a) 相等于 np.sort(a, axis=0)。

sort_complex(a)

对复数按照先实部后虚部的顺序进行排序。

partition(a, kth[, axis, kind, order])

指定一个数,对数组进行分区

argpartition(a, kth[, axis, kind, order])

可以通过关键字 kind 指定算法沿着指定轴对数组进行分区

7.2 筛选
  • numpy.argmax() 和 numpy.argmin()

numpy.argmax() 和 numpy.argmin()函数分别沿给定轴返回最大和最小元素的索引。

  • numpy.nonzero()

numpy.nonzero() 函数返回输入数组中非零元素的索引。

  • numpy.where()

numpy.where() 函数返回输入数组中满足给定条件的元素的索引。

  • numpy.extract()

numpy.extract() 函数根据某个条件从数组中抽取元素,返回满条件的元素。

8. 数组迭代

NumPy 迭代器对象 numpy.nditer 提供了一种灵活访问一个或者多个数组元素的方式。

import numpy as np

a = np.arange(6).reshape(2,3)
print (a)
print ('\n')
print ('迭代输出:')
for x in np.nditer(a):
    print (x, end=", " )
print ('\n')

执行结果

[[0 1 2]
 [3 4 5]]


迭代输出:
0, 1, 2, 3, 4, 5,

控制遍历顺序

  • for x in np.nditer(a, order='F'):Fortran order,即是列序优先;

  • for x in np.nditer(a.T, order='C'):C order,即是行序优先;

import numpy as np

a = np.arange(0,60,5)
a = a.reshape(3,4)
print ('原始数组是:')
print (a)
print ('\n')
print ('以 C 风格顺序排序:')
for x in np.nditer(a, order =  'C'):
    print (x, end=", " )
print ('\n')
print ('以 F 风格顺序排序:')
for x in np.nditer(a, order =  'F'):
    print (x, end=", " )

执行结果

原始数组是:
[[ 0  5 10 15]
 [20 25 30 35]
 [40 45 50 55]]


以 C 风格顺序排序:
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55,

以 F 风格顺序排序:
0, 20, 40, 5, 25, 45, 10, 30, 50, 15, 35, 55,

使用外部循环

nditer类的构造器拥有flags参数,常用的值有:

参数

描述

c_index

可以跟踪 C 顺序的索引

f_index

可以跟踪 Fortran 顺序的索引

multi-index

每次迭代可以跟踪一种索引类型

external_loop

给出的值是具有多个值的一维数组,而不是零维数组

read-write

遍历数组的同时,实现对数组元素值得修改

write-only

遍历数组的同时,实现对数组元素值得修改

import numpy as np

a = np.arange(0,60,5)
a = a.reshape(3,4)
print ('原始数组是:')
print (a)
print ('\n')
for x in np.nditer(a, op_flags=['readwrite']):
    x[...]=2*x
print ('修改后的数组是:')
print (a)

执行结果

原始数组是:
[[ 0  5 10 15]
 [20 25 30 35]
 [40 45 50 55]]


修改后的数组是:
[[  0  10  20  30]
 [ 40  50  60  70]
 [ 80  90 100 110]]

9. Numpy计算

9.1 数学/算数函数

函数

描述

numpy.sqrt(array)

平方根函数

numpy.exp(array)

指数函数

numpy.abs/fabs(array)

绝对值

numpy.square(array)

计算各元素的平方 等于array**2

numpy.log/log10/log2(array)

对数函数

numpy.sign(array)

计算各元素的正负号

numpy.isnan(array)

判断各元素是否为NaN

numpy.isinf(array)

判断各元素是否为Inf

numpy.cos/cosh/sin/sinh/tan/tanh(array)

三角函数

numpy.modf(array)

整数和小数分离,返回两个数组

numpy.ceil(array)

向上取整

numpy.floor(array)

向下取整

numpy.rint(array)

四舍五入

numpy.trunc(array)

向0取整

numpy.add(array1,array2)

元素级加法

numpy.subtract(array1,array2)

元素级减法

numpy.multiply(array1,array2)

元素级乘法

numpy.divide(array1,array2)

元素级除法

numpy.power(array1,array2)

元素级指数

numpy.maximum/minimum(array1,aray2)

元素级最大/最小值

numpy.fmax/fmin(array1,array2)

元素级最大/最小值,忽略NaN

numpy.mod(array1,array2)

元素级求模

numpy.copysign(array1,array2)

将第二个数组值得正负号复制给第一个数组

numpy.greater/greater_equal(array1,array2)

元素级比较运算,产生布尔型数组

numpy.less/less_equal (array1,array2)

元素级比较运算,产生布尔型数组

numpy.equal/not_equal (array1,array2)

元素级比较运算,产生布尔型数组

numpy.logical_end/logical_or/logic_xor(array1,array2)

元素级的真值逻辑运算

7.2 统计函数

函数

描述

numpy.amin()

用于计算数组中的元素沿指定轴的最小值

numpy.amax()

用于计算数组中的元素沿指定轴的最大值

numpy.ptp()

计算数组中元素最大值与最小值的差

numpy.percentile()

计算百分位数

numpy.median()

计算中位数

numpy.mean()

计算算数平均值

numpy.average()

根据在另一个数组中给出的各自的权重计算数组中元素的加权平均值

numpy.std()

计算标准差

numpy.var()

计算方差

9.3 字符串函数

函数

描述

numpy.char.add()

对两个数组的逐个字符串元素进行连接

numpy.char.multiply()

返回按元素多重连接后的字符串

numpy.char.center()

居中字符串

numpy.char.capitalize()

将字符串第一个字母转换为大写

numpy.char.title()

将字符串的每个单词的第一个字母转换为大写

numpy.char.lower()

数组元素转换为小写

numpy.char.upper()

数组元素转换为大写

numpy.char.split()

指定分隔符对字符串进行分割,并返回数组列表

numpy.char.splitlines()

返回元素中的行列表,以换行符分割

numpy.char.strip()

移除元素开头或者结尾处的特定字符

numpy.char.join()

通过指定分隔符来连接数组中的元素

numpy.char.replace()

使用新字符串替换字符串中的所有子字符串

numpy.char.decode()

数组元素依次调用str.decode

numpy.char.encode()

数组元素依次调用str.encode

10. 数组的拼接

10.1 根据轴连接

np.concatenate()

a = np.array([[1,2],[3,4]])
b = np.array([[5,6],[7,8]])
print(a)
print(b)
print ('\n')
# 要求a,b两个数组的维度相同
print ('沿轴 0 连接两个数组:')
print (np.concatenate((a,b),axis= 0))
print ('\n')
print ('沿轴 1 连接两个数组:')
print (np.concatenate((a,b),axis = 1))

执行结果

[[1 2]
 [3 4]]
[[5 6]
 [7 8]]

沿轴 0 连接两个数组:
[[1 2]
 [3 4]
 [5 6]
 [7 8]]


沿轴 1 连接两个数组:
[[1 2 5 6]
 [3 4 7 8]]
10.2 根据轴堆叠

np.stack()

a = np.array([[1,2],[3,4]])
b = np.array([[5,6],[7,8]])
print(a)
print(b)
print ('\n')

print ('沿轴 0 连接两个数组:')
print (np.stack((a,b),axis= 0))
print ('\n')
print ('沿轴 1 连接两个数组:')
print (np.stack((a,b),axis = 1))

执行结果

[[1 2]
 [3 4]]
[[5 6]
 [7 8]]


沿轴 0 连接两个数组:
[[[1 2]
  [3 4]]

 [[5 6]
  [7 8]]]


沿轴 1 连接两个数组:
[[[1 2]
  [5 6]]

 [[3 4]
  [7 8]]]
10.3 矩阵垂直/水平拼接
v1 = [[0,1,2,3,4,5],
[6,7,8,9,10,11]]

v2 = [[12,13,14,15,16,17],
[18,19,20,21,22,23]]

print(v1)
print(v2)
print('\n')

# 矩阵垂直拼接
result = np.vstack((v1,v2))
print(result)

v1 = [[0,1,2,3,4,5],
[6,7,8,9,10,11]]

v2 = [[12,13,14,15,16,17],
[18,19,20,21,22,23]]

# 矩阵水平拼接
result = np.hstack((v1,v2))
print(result)

执行结果

[[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11]]
[[12, 13, 14, 15, 16, 17], [18, 19, 20, 21, 22, 23]]


[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]
[[ 0  1  2  3  4  5 12 13 14 15 16 17]
 [ 6  7  8  9 10 11 18 19 20 21 22 23]]

注意:

  • concatenate不增加新的维度, stack在原来的维度上增加了一个维度

  • hstack 和vstack是和concatenate函数是一样的,不增加新的维度

a = np.array([[1,2],[3,4]])
b = np.array([[5,6],[7,8]])
print(a)
print(b)
print ('\n')
print ('concatenate:')
print (np.concatenate((a,b),axis= 0))
print ('\n')

print ('stack:')
print (np.stack((a,b),axis= 0))
print ('\n')

print ('vstack:')
print(np.vstack((a,b)))

执行结果

[[1 2]
 [3 4]]
[[5 6]
 [7 8]]


concatenate:
[[1 2]
 [3 4]
 [5 6]
 [7 8]]


stack:
[[[1 2]
  [3 4]]

 [[5 6]
  [7 8]]]


vstack:
[[1 2]
 [3 4]
 [5 6]
 [7 8]]

11. 数组的分割

split将一个数组分割为多个子数组

参数说明:

  • ary:被分割的数组

  • indices_or_sections:如果是一个整数,就用该数平均切分,如果是一个数组,为沿轴切分的位置(左开右闭)

  • axis:沿着哪个维度进行切向,默认为0,横向切分。为1时,纵向切分

arr = np.arange(9).reshape(3,3)
print(arr)
print('\n')
print ('将数组分为三个大小相等的子数组:\n')
b = np.split(arr,3)
print (b)

执行结果

[[0 1 2]
 [3 4 5]
 [6 7 8]]


将数组分为三个大小相等的子数组:

[array([[0, 1, 2]]), array([[3, 4, 5]]), array([[6, 7, 8]])]

水平轴分割数组,也可使用numpy.hsplit

harr = np.floor(10 * np.random.random((2, 6)))
print ('原array:')
print(harr)
print ('拆分后:')
print(np.split(harr,2,1))
print(np.hsplit(harr, 2))

执行结果

原array:
[[1. 9. 7. 7. 1. 9.]
 [8. 1. 3. 2. 6. 9.]]
拆分后:
[array([[1., 9., 7.],
       [8., 1., 3.]]), array([[7., 1., 9.],
       [2., 6., 9.]])]
[array([[1., 9., 7.],
       [8., 1., 3.]]), array([[7., 1., 9.],
       [2., 6., 9.]])]

垂直轴分割数组,也可以使用numpy.vsplit

harr = np.floor(10 * np.random.random((2, 6)))
print ('原array:')
print(harr)
print ('拆分后:')
print(np.split(harr,2,0))
print(np.vsplit(harr, 2))

执行结果

原array:
[[6. 1. 1. 1. 9. 9.]
 [0. 7. 8. 1. 4. 6.]]
拆分后:
[array([[6., 1., 1., 1., 9., 9.]]), array([[0., 7., 8., 1., 4., 6.]])]
[array([[6., 1., 1., 1., 9., 9.]]), array([[0., 7., 8., 1., 4., 6.]])]

注意:spli函数只能用于均等分割,如果不能均等分割则会报错:array split does not result in an equal division

12. 数组中nan和inf

inf 表示无穷大,需要使用 float(‘inf’) 函数来转化,那么对应的就有 float('-inf') 表示无 穷小了。这样你就可以使用任意数来判断和它的关系了。 那什么时候会出现inf呢? 比如一个数字除以0,Python中会报错,但是numpy中会是一个inf或者-inf

nan在 pandans 中常见,表示缺失的数据,所以一般用 nan 来表示。任何与其做运算结果都是 nan

a = np.nan
b = np.inf
print(a,type(a))
print(b,type(b))
print('\n')
# --判断数组中为nan的个数
t = np.arange(24,dtype=float).reshape(4,6)
# print(t)
# print('\n')

t[2, 3:5] = np.NaN


t[1, 2] = np.inf
print(t)
print('\n')


print(np.isnan(t))
print('\n')
print(np.isinf(t))

执行结果

nan <class 'float'>
inf <class 'float'>


[[ 0.  1.  2.  3.  4.  5.]
 [ 6.  7. inf  9. 10. 11.]
 [12. 13. 14. nan nan 17.]
 [18. 19. 20. 21. 22. 23.]]


[[False False False False False False]
 [False False False False False False]
 [False False False  True  True False]
 [False False False False False False]]


[[False False False False False False]
 [False False  True False False False]
 [False False False False False False]
 [False False False False False False]]

13. 二维数组转置

transpose

a = np.arange(12).reshape(3,4)
print ('原数组:')
print (a )
print ('\n')
print ('对换数组:')
print (np.transpose(a))

执行结果

原数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]


对换数组:
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]

T转置

a = np.arange(12).reshape(3,4)
print ('原数组:')
print (a)
print ('\n')
print ('转置数组:')
print (a.T)

执行结果

原数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]


转置数组:
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]

swapaxes

a = np.arange(12).reshape(3,4)
re = a.swapaxes(1,0)
print ('原数组:')
print (a)
print ('\n')
print ('调用 swapaxes 函数后的数组:')
print (re)

执行结果

原数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]


调用 swapaxes 函数后的数组:
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]

5.2 Pandas

PandasPython 的核心数据分析支持库,提供了快速、灵活、明确的数据结构,旨在简单、直观地处理关系型、标记型数据。Pandas 的目标是成为 Python 数据分析实践与实战的必备高级工具,其长远目标是成为最强大、最灵活、可以支持任何语言的开源数据分析工具

Pandas 适用于处理以下类型的数据:

  • 与 SQL 或 Excel 表类似的,含异构列的表格数据;

  • 有序和无序(非固定频率)的时间序列数据;

  • 带行列标签的矩阵数据,包括同构或异构型数据;

  • 任意其它形式的观测、统计数据集, 数据转入 Pandas 数据结构时不必事先标记。

1. 安装

使用Anaconda安装

conda install pandas

使用PyPI安装

pip install pandas

2.数据结构简介

2.1 Series

Series 是带标签的一维数组,可存储整数、浮点数、字符串、Python 对象等类型的数据。轴标签统称为索引。调用 pd.Series 函数即可创建 Series:

s = pd.Series(data, index=index)

data支持一下数据类型

  • Python字典

  • 多维数组

  • 标量值

index是轴标签列表,也就是索引

使用Python字典创建Series

d = {'a':1, 'b':2, 'c':3}
pd.Series(d)

a    1
b    2
c    3
dtype: int64

使用多维数组创建Series

pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])

a   -1.933183
b    0.300629
c    0.386382
d    0.707157
e   -0.515151
dtype: float64

使用标量值创建Series

pd.Series(5., index=['a', 'b', 'c', 'd', 'e'])

a    5.0
b    5.0
c    5.0
d    5.0
e    5.0
dtype: float64
2.2 DataFrame

DataFrame是由多种类型的列构成的二维标签数据结构,类似于 Excel 、SQL 表,或 Series 对象构成的字典。DataFrame 是最常用的 Pandas 对象,与 Series 一样,DataFrame 支持多种类型的输入数据:

  • 一维 ndarray、列表、字典、Series 字典

  • 二维 numpy.ndarray

  • 结构多维数组或记录多维数组

  • Series

  • DataFrame

除了数据,还可以有选择地传递 index(行标签)和 columns(列标签)参数。传递了索引或列,就可以确保生成的 DataFrame 里包含索引或列。Series 字典加上指定索引时,会丢弃与传递的索引不匹配的所有数据。

用 Series 字典或字典生成 DataFrame

d = {'one': pd.Series([1., 2., 3.], index=['a', 'b', 'c']),'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}

pd.DataFrame(d)

pd.DataFrame(d, index=['d', 'b', 'a'])

执行结果

    one two
a   1.0 1.0
b   2.0 2.0
c   3.0 3.0
d   NaN 4.0

    one two
d   NaN 4.0
b   2.0 2.0
a   1.0 1.0

    two three
d   4.0 NaN
b   2.0 NaN
a   1.0 NaN

注意:指定的列与字典一起传递时,传递的列会覆盖字典的键。

用多维数组字典、列表字典生成 DataFrame

d = {'one': [1., 2., 3., 4.], 'two': [4., 3., 2., 1.]}
pd.DataFrame(d, index=['a', 'b', 'c', 'd'])

执行结果

    one two
a   1.0 4.0
b   2.0 3.0
c   3.0 2.0
d   4.0 1.0

用结构多维数组或记录多维数组生成 DataFrame

data = np.zeros((2, ), dtype=[('A', 'i4'), ('B', 'f4'), ('C', 'a10')])

data[:] = [(1, 2., 'Hello'), (2, 3., "World")]

pd.DataFrame(data, index=['first', 'second'], columns=['C', 'A', 'B'])

执行结果

        C           A   B
first   b'Hello'    1   2.0
second  b'World'    2   3.0

用列表字典生成 DataFrame

data = [{'a': 1, 'b': 2}, {'a': 5, 'b': 10, 'c': 20}]

pd.DataFrame(data, index=['first', 'second'])

执行结果

        a   b   c
first   1   2   NaN
second  5   10  20.0

用元组字典生成 DataFrame

pd.DataFrame({('a', 'b'): {('A', 'B'): 1, ('A', 'C'): 2},
                ('a', 'a'): {('A', 'C'): 3, ('A', 'B'): 4},
                ('a', 'c'): {('A', 'B'): 5, ('A', 'C'): 6},
                ('b', 'a'): {('A', 'C'): 7, ('A', 'B'): 8},
                ('b', 'b'): {('A', 'D'): 9, ('A', 'B'): 10}})

执行结果

        a           b
        b   a   c   a   b
A   B   1.0 4.0 5.0 8.0 10.0
    C   2.0 3.0 6.0 7.0 NaN
    D   NaN NaN NaN NaN 9.0

3. 基础入门

3.1 查看数据

查看头部和尾部数据

data_index = pd.date_range(start='1/1/2020', periods=100)

df = pd.DataFrame(np.random.randn(500).reshape(100,5), index=data_index, columns=['A','B','C','D','E'])

df.head()

            A           B           C           D           E
2020-01-01  -1.071938   -0.388269   0.404821    -0.674684   1.082843
2020-01-02  -0.347211   1.977614    -0.050738   -0.046957   0.246992
2020-01-03  0.129989    -0.445965   -0.235185   -0.265440   -1.086967
2020-01-04  0.616785    0.262000    -1.290709   1.218618    0.720770
2020-01-05  -2.115399   1.179655    -0.290259   0.781388    1.021759

df.tail(3)

            A           B           C           D           E
2020-04-07  1.107176    -1.507005   0.442401    0.767987    0.772502
2020-04-08  -1.543107   0.356048    -0.246524   1.469017    -0.282765
2020-04-09  -0.242034   0.494868    1.958897    -0.281020   -1.177761

显示索引与列名

df.index

DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
               '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08',
               '2020-01-09', '2020-01-10', '2020-01-11', '2020-01-12',
               '2020-01-13', '2020-01-14', '2020-01-15', '2020-01-16',
               '2020-01-17', '2020-01-18', '2020-01-19', '2020-01-20',
               '2020-01-21', '2020-01-22', '2020-01-23', '2020-01-24',
               '2020-01-25', '2020-01-26', '2020-01-27', '2020-01-28',
               '2020-01-29', '2020-01-30', '2020-01-31', '2020-02-01',
               '2020-02-02', '2020-02-03', '2020-02-04', '2020-02-05',
               '2020-02-06', '2020-02-07', '2020-02-08', '2020-02-09',
               '2020-02-10', '2020-02-11', '2020-02-12', '2020-02-13',
               '2020-02-14', '2020-02-15', '2020-02-16', '2020-02-17',
               '2020-02-18', '2020-02-19', '2020-02-20', '2020-02-21',
               '2020-02-22', '2020-02-23', '2020-02-24', '2020-02-25',
               '2020-02-26', '2020-02-27', '2020-02-28', '2020-02-29',
               '2020-03-01', '2020-03-02', '2020-03-03', '2020-03-04',
               '2020-03-05', '2020-03-06', '2020-03-07', '2020-03-08',
               '2020-03-09', '2020-03-10', '2020-03-11', '2020-03-12',
               '2020-03-13', '2020-03-14', '2020-03-15', '2020-03-16',
               '2020-03-17', '2020-03-18', '2020-03-19', '2020-03-20',
               '2020-03-21', '2020-03-22', '2020-03-23', '2020-03-24',
               '2020-03-25', '2020-03-26', '2020-03-27', '2020-03-28',
               '2020-03-29', '2020-03-30', '2020-03-31', '2020-04-01',
               '2020-04-02', '2020-04-03', '2020-04-04', '2020-04-05',
               '2020-04-06', '2020-04-07', '2020-04-08', '2020-04-09'],
              dtype='datetime64[ns]', freq='D')

df.columns

Index(['A', 'B', 'C', 'D', 'E'], dtype='object')

describe()快速查看数据摘要

df.describe()

        A           B           C           D           E
count   100.000000  100.000000  100.000000  100.000000  100.000000
mean    -0.189421   0.092631    -0.002600   -0.027843   0.156094
std     0.988333    1.087173    0.930738    0.933235    1.026329
min     -2.524124   -2.034074   -2.002786   -1.935007   -1.983693
25%     -0.840493   -0.735936   -0.673191   -0.712899   -0.433909
50%     -0.180693   0.009894    0.032741    -0.076358   0.096075
75%     0.492854    0.781675    0.587729    0.595442    0.728967
max     2.080747    3.577149    2.466595    2.611626    3.322429

转置数据

df.head(3).T

    2020-01-01 00:00:00 2020-01-02 00:00:00 2020-01-03 00:00:00
A   -1.071938           -0.347211           0.129989
B   -0.388269           1.977614            -0.445965
C   0.404821            -0.050738           -0.235185
D   -0.674684           -0.046957           -0.265440
E   1.082843            0.246992            -1.086967

按轴排序

df.sort_index(axis=1, ascending=False).head(3)

            E           D           C           B           A
2020-01-01  1.082843    -0.674684   0.404821    -0.388269   -1.071938
2020-01-02  0.246992    -0.046957   -0.050738   1.977614    -0.347211
2020-01-03  -1.086967   -0.265440   -0.235185   -0.445965   0.129989

按值排序

df.sort_values(by='C', ascending=False).head(3)

            A           B           C           D           E
2020-01-14  0.340972    -0.726843   2.466595    0.766566    0.825533
2020-04-09  -0.242034   0.494868    1.958897    -0.281020   -1.177761
2020-04-06  0.847497    0.408494    1.748987    -1.200556   -0.192185
3.2 选择

提醒: 选择、设置标准 Python / Numpy 的表达式已经非常直观,交互也很方便,但对于生产代码,还是推荐优化过的 Pandas 数据访问方法:.at.iat.loc.iloc

测试数据

data_index = pd.date_range(start='1/1/2020', periods=8)

df = pd.DataFrame(np.random.randn(32).reshape(8,4), index=data_index, columns=['A','B','C','D'])

            A           B           C           D
2020-01-01  -0.534738   1.198495    1.099884    0.646788
2020-01-02  0.661826    0.633155    -0.467720   -1.329015
2020-01-03  -0.084597   0.575203    -0.080986   -0.476005
2020-01-04  -1.013491   0.168052    0.445559    0.308987
2020-01-05  1.193184    1.078068    -0.968468   -0.024408
2020-01-06  -1.451905   -1.124250   0.366782    -0.711344
2020-01-07  0.951567    -0.333190   1.404569    0.574293
2020-01-08  1.007324    -1.543192   -0.451113   -0.944006

获取数据

选择单列,生成Series,等效于df.A

df['A']

2020-01-01   -0.534738
2020-01-02    0.661826
2020-01-03   -0.084597
2020-01-04   -1.013491
2020-01-05    1.193184
2020-01-06   -1.451905
2020-01-07    0.951567
2020-01-08    1.007324
Freq: D, Name: A, dtype: float64

切片[]选择行

df[1:3]

            A           B           C           D
2020-01-02  0.661826    0.633155    -0.467720   -1.329015
2020-01-03  -0.084597   0.575203    -0.080986   -0.476005


df['2020-01-03':'2020-01-05']

            A           B           C           D
2020-01-03  -0.084597   0.575203    -0.080986   -0.476005
2020-01-04  -1.013491   0.168052    0.445559    0.308987
2020-01-05  1.193184    1.078068    -0.968468   -0.024408

按标签选择

用标签提取一行数据

df.loc['2020-01-03']

A   -0.084597
B    0.575203
C   -0.080986
D   -0.476005
Name: 2020-01-03 00:00:00, dtype: float64

用标签选择多列数据

df.loc[:,['A','D']]

            A           D
2020-01-01  -0.534738   0.646788
2020-01-02  0.661826    -1.329015
2020-01-03  -0.084597   -0.476005
2020-01-04  -1.013491   0.308987
2020-01-05  1.193184    -0.024408
2020-01-06  -1.451905   -0.711344
2020-01-07  0.951567    0.574293
2020-01-08  1.007324    -0.944006

用标签切片,包含行与列的结束点

df.loc['2020-01-03':'2020-01-06', 'B':'D']

            B           C           D
2020-01-03  0.575203    -0.080986   -0.476005
2020-01-04  0.168052    0.445559    0.308987
2020-01-05  1.078068    -0.968468   -0.024408
2020-01-06  -1.124250   0.366782    -0.711344

提取标量值

df.loc['2020-01-03', 'B']

0.5752031238478458

快速访问标量值也可以使用df.at

df.loc['2020-01-03'].at['B']

0.5752031238478458

按照位置选择

用整数位置选择

df.iloc[3]

A   -1.013491
B    0.168052
C    0.445559
D    0.308987
Name: 2020-01-04 00:00:00, dtype: float64

用整数切片

df.iloc[3:5, 1:3]

            B           C
2020-01-04  0.168052    0.445559
2020-01-05  1.078068    -0.968468

用整数列表按照位置切片

df.iloc[[1,4,2], [3,1]]

            D           B
2020-01-02  -1.329015   0.633155
2020-01-05  -0.024408   1.078068
2020-01-03  -0.476005   0.575203

显示整行切片

df.iloc[1:3, :]

            A           B           C           D
2020-01-02  0.661826    0.633155    -0.467720   -1.329015
2020-01-03  -0.084597   0.575203    -0.080986   -0.476005

显示整列切片

df.iloc[:, 1:3]

            B           C
2020-01-01  1.198495    1.099884
2020-01-02  0.633155    -0.467720
2020-01-03  0.575203    -0.080986
2020-01-04  0.168052    0.445559
2020-01-05  1.078068    -0.968468
2020-01-06  -1.124250   0.366782
2020-01-07  -0.333190   1.404569
2020-01-08  -1.543192   -0.451113

显示标量值

df.iloc[2,2]

-0.08098568559057222

快速访问标量值也可以使用df.iat

df.iat[2,2]

-0.08098568559057222

布尔索引

用单列的值选择数据

df[df.A>0]

            A           B           C           D
2020-01-02  0.661826    0.633155    -0.467720   -1.329015
2020-01-05  1.193184    1.078068    -0.968468   -0.024408
2020-01-07  0.951567    -0.333190   1.404569    0.574293
2020-01-08  1.007324    -1.543192   -0.451113   -0.944006

选择整个DataFrame里满足条件的值

df[df>1]

            A           B           C           D
2020-01-01  NaN         1.198495    1.099884    NaN
2020-01-02  NaN         NaN         NaN         NaN
2020-01-03  NaN         NaN         NaN         NaN
2020-01-04  NaN         NaN         NaN         NaN
2020-01-05  1.193184    1.078068    NaN         NaN
2020-01-06  NaN         NaN         NaN         NaN
2020-01-07  NaN         NaN         1.404569    NaN
2020-01-08  1.007324    NaN         NaN         NaN

isin()筛选

df2 = df.copy()

df2['E'] = ['A', 'C', 'B', 'C', 'D', 'A', 'C', 'C']

df2

            A           B           C           D           E
2020-01-01  -0.534738   1.198495    1.099884    0.646788    A
2020-01-02  0.661826    0.633155    -0.467720   -1.329015   C
2020-01-03  -0.084597   0.575203    -0.080986   -0.476005   B
2020-01-04  -1.013491   0.168052    0.445559    0.308987    C
2020-01-05  1.193184    1.078068    -0.968468   -0.024408   D
2020-01-06  -1.451905   -1.124250   0.366782    -0.711344   A
2020-01-07  0.951567    -0.333190   1.404569    0.574293    C
2020-01-08  1.007324    -1.543192   -0.451113   -0.944006   C

df2[df2['E'].isin(['B', 'C', 'F'])]

            A           B           C           D           E
2020-01-02  0.661826    0.633155    -0.467720   -1.329015   C
2020-01-03  -0.084597   0.575203    -0.080986   -0.476005   B
2020-01-04  -1.013491   0.168052    0.445559    0.308987    C
2020-01-07  0.951567    -0.333190   1.404569    0.574293    C
2020-01-08  1.007324    -1.543192   -0.451113   -0.944006   C

赋值

用索引自动对齐,新增列数据

s = pd.Series([1,2,3,4,5,6,7,8], index=pd.date_range(start='1/1/2020',periods=8))

df['E'] = s

df

            A           B           C           D           E
2020-01-01  -0.534738   1.198495    1.099884    0.646788    1
2020-01-02  0.661826    0.633155    -0.467720   -1.329015   2
2020-01-03  -0.084597   0.575203    -0.080986   -0.476005   3
2020-01-04  -1.013491   0.168052    0.445559    0.308987    4
2020-01-05  1.193184    1.078068    -0.968468   -0.024408   5
2020-01-06  -1.451905   -1.124250   0.366782    -0.711344   6
2020-01-07  0.951567    -0.333190   1.404569    0.574293    7
2020-01-08  1.007324    -1.543192   -0.451113   -0.944006   8

按标签赋值

df.loc['2020-01-01','B'] = 0

df

            A           B           C           D           E
2020-01-01  -0.534738   0.000000    1.099884    0.646788    1
2020-01-02  0.661826    0.633155    -0.467720   -1.329015   2
2020-01-03  -0.084597   0.575203    -0.080986   -0.476005   3
2020-01-04  -1.013491   0.168052    0.445559    0.308987    4
2020-01-05  1.193184    1.078068    -0.968468   -0.024408   5
2020-01-06  -1.451905   -1.124250   0.366782    -0.711344   6
2020-01-07  0.951567    -0.333190   1.404569    0.574293    7
2020-01-08  1.007324    -1.543192   -0.451113   -0.944006   8

按位置赋值

df.iloc[2,2] = 0

df

            A           B           C           D           E
2020-01-01  -0.534738   0.000000    1.099884    0.646788    1
2020-01-02  0.661826    0.633155    -0.467720   -1.329015   2
2020-01-03  -0.084597   0.575203    0.000000    -0.476005   3
2020-01-04  -1.013491   0.168052    0.445559    0.308987    4
2020-01-05  1.193184    1.078068    -0.968468   -0.024408   5
2020-01-06  -1.451905   -1.124250   0.366782    -0.711344   6
2020-01-07  0.951567    -0.333190   1.404569    0.574293    7
2020-01-08  1.007324    -1.543192   -0.451113   -0.944006   8

用Numpy数组赋值

df['E'] = np.array([0] * len(df))

df

            A           B           C           D           E
2020-01-01  -0.534738   0.000000    1.099884    0.646788    0
2020-01-02  0.661826    0.633155    -0.467720   -1.329015   0
2020-01-03  -0.084597   0.575203    0.000000    -0.476005   0
2020-01-04  -1.013491   0.168052    0.445559    0.308987    0
2020-01-05  1.193184    1.078068    -0.968468   -0.024408   0
2020-01-06  -1.451905   -1.124250   0.366782    -0.711344   0
2020-01-07  0.951567    -0.333190   1.404569    0.574293    0
2020-01-08  1.007324    -1.543192   -0.451113   -0.944006   0

where条件赋值

df[df>0] = -df

df

            A           B           C           D           E
2020-01-01  -0.534738   0.000000    -1.099884   -0.646788   0
2020-01-02  -0.661826   -0.633155   -0.467720   -1.329015   0
2020-01-03  -0.084597   -0.575203   0.000000    -0.476005   0
2020-01-04  -1.013491   -0.168052   -0.445559   -0.308987   0
2020-01-05  -1.193184   -1.078068   -0.968468   -0.024408   0
2020-01-06  -1.451905   -1.124250   -0.366782   -0.711344   0
2020-01-07  -0.951567   -0.333190   -1.404569   -0.574293   0
2020-01-08  -1.007324   -1.543192   -0.451113   -0.944006   0
3.3 缺失值

Pandas使用np.nan表示缺失值。计算时,默认不处理缺失值。

测试数据

data_index = pd.date_range(start='1/1/2020', periods=6)

df = pd.DataFrame(np.random.randn(24).reshape(6,4), index=data_index, columns=['A','B','C','D'])

df.iloc[1:4, [2,3]] = np.nan

df.loc['2020-01-06', ['A','C']] = np.nan

df

            A           B           C           D
2020-01-01  -0.522267   1.878701    -0.749467   0.087433
2020-01-02  0.689572    -0.175677   NaN         NaN
2020-01-03  -0.622268   -0.172894   NaN         NaN
2020-01-04  -0.273200   -0.763474   NaN         NaN
2020-01-05  -1.370132   -0.222186   1.114736    -2.165299
2020-01-06  NaN         -0.881161   NaN         1.708045

删除所有包含缺失值的列

df.dropna(axis=1, how='any')

            B
2020-01-01  1.878701
2020-01-02  -0.175677
2020-01-03  -0.172894
2020-01-04  -0.763474
2020-01-05  -0.222186
2020-01-06  -0.881161

填充缺失值

df.fillna(value=df.max())

            A           B           C           D
2020-01-01  -0.522267   1.878701    -0.749467   0.087433
2020-01-02  0.689572    -0.175677   1.114736    1.708045
2020-01-03  -0.622268   -0.172894   1.114736    1.708045
2020-01-04  -0.273200   -0.763474   1.114736    1.708045
2020-01-05  -1.370132   -0.222186   1.114736    -2.165299
2020-01-06  0.689572    -0.881161   1.114736    1.708045

提取缺失值

df.isna()

            A           B   C       D
2020-01-01  False   False   False   False
2020-01-02  False   False   True    True
2020-01-03  False   False   True    True
2020-01-04  False   False   True    True
2020-01-05  False   False   False   False
2020-01-06  True    False   True    False
3.4 运算

统计

描述性统计

df.mean(axis=0)

2020-01-01    0.173600
2020-01-02    0.256948
2020-01-03   -0.397581
2020-01-04   -0.518337
2020-01-05   -0.660720
2020-01-06    0.413442
Freq: D, dtype: float64

不同维度对象运算时,要先对齐。 此外,Pandas 自动沿指定维度广播。shift的作用是将数据移动到指定的位置

data_index = pd.date_range(start='1/1/2020', periods=6)

df = pd.DataFrame(np.random.randn(24).reshape(6,4), index=data_index, columns=['A','B','C','D'])

df

            A           B           C           D
2020-01-01  2.247186    -0.547146   -0.581378   -0.757834
2020-01-02  2.158050    -0.526511   1.135555    0.388816
2020-01-03  0.132194    1.810191    0.612350    -0.616597
2020-01-04  1.323747    -0.981873   -0.311311   -1.956533
2020-01-05  0.720286    -0.686399   0.092560    -0.652112
2020-01-06  1.537573    0.916894    -1.132592   -0.280569


s = pd.Series([1, 3, 5, np.nan, 6, 8], index=df.index).shift(2)

s

2020-01-01    NaN
2020-01-02    NaN
2020-01-03    1.0
2020-01-04    3.0
2020-01-05    5.0
2020-01-06    NaN
Freq: D, dtype: float64

df.sub(s, axis=0)

    A   B   C   D
2020-01-01  NaN NaN NaN NaN
2020-01-02  NaN NaN NaN NaN
2020-01-03  -0.867806   0.810191    -0.387650   -1.616597
2020-01-04  -1.676253   -3.981873   -3.311311   -4.956533
2020-01-05  -4.279714   -5.686399   -4.907440   -5.652112
2020-01-06  NaN NaN NaN NaN

常见描述和汇总统计方法

方法

说明

count

非NA值得数量

describe

针对Series或各DataFrame列计算汇总统计

min、max

计算最大值和最小值

argmin、argmax

获取最大值和最小值的索引位置

idxmin、idxmax

获取最大值和最小值的索引值

quantile

计算样本分位数

sum

值的总和

mean

值的平均数

median

值的算术中位数

mad

根据平均值计算平均绝对离差

var

样本值的方差

std

样本值的标准差

skew

样本值的偏度(三阶矩)

kurt

样本值的峰度(四阶矩)

cumsum

样本值的累计和

cummin、cummax

样本值的累计最大值和累计最小值

cumpord

样本值的累计积

diff

计算一阶差分

pct_change

计算百分数变化

Apply函数

Apply函数处理数据

data_index = pd.date_range(start='1/1/2020', periods=6)

df = pd.DataFrame(np.random.randn(24).reshape(6,4), index=data_index, columns=['A','B','C','D'])

df

    A   B   C   D
2020-01-01  -0.314069   -1.640740   -0.234538   -1.444483
2020-01-02  -0.663693   0.204120    1.047381    1.161871
2020-01-03  -0.156265   0.911876    0.516399    1.178329
2020-01-04  -0.032716   -0.699291   0.330868    0.276858
2020-01-05  -0.322746   -1.197176   -2.257491   0.542459
2020-01-06  -0.768759   -0.660178   1.553387    -0.150967

df.apply(np.sum)

A   -2.258248
B   -3.081389
C    0.956006
D    1.564065
dtype: float64


df.apply(lambda x: x.max()-x.min())

A    0.736043
B    2.552616
C    3.810878
D    2.622812
dtype: float64
3.5 合并

concat

Pandas 提供了多种将 Series、DataFrame 对象组合在一起的功能,用索引与关联代数功能的多种设置逻辑,可执行连接(join)与合并(merge)操作。

concat用于连接Pandas对象

df = pd.DataFrame(np.random.randn(10, 4))

df

    0           1           2           3
0   -0.304959   0.175322    -1.587665   -0.557863
1   -1.717156   -0.464027   -2.306315   -1.565576
2   0.259886    -1.113886   -0.028438   0.204850
3   0.271444    -0.763516   0.479202    -0.222412
4   -0.595290   -0.041597   -0.405921   0.177898
5   -0.646104   -0.682442   -0.457514   0.665751
6   0.337856    -0.198607   -0.072115   1.664769
7   -1.183995   -0.394815   0.392509    -1.065970
8   -0.667517   0.114392    2.043012    -1.554584
9   0.167326    -0.134128   -0.345591   0.870225

# 分解成多个组
pieces = [df[:3], df[3:7], df[7:]]

# 合并
pd.concat(pieces)

    0           1           2           3
0   -0.304959   0.175322    -1.587665   -0.557863
1   -1.717156   -0.464027   -2.306315   -1.565576
2   0.259886    -1.113886   -0.028438   0.204850
3   0.271444    -0.763516   0.479202    -0.222412
4   -0.595290   -0.041597   -0.405921   0.177898
5   -0.646104   -0.682442   -0.457514   0.665751
6   0.337856    -0.198607   -0.072115   1.664769
7   -1.183995   -0.394815   0.392509    -1.065970
8   -0.667517   0.114392    2.043012    -1.554584
9   0.167326    -0.134128   -0.345591   0.870225

注意:将列添加到dataFrame相对较快。但是,添加行需要一个副本,可能需要更高昂的代价。因此,尽量将构建的记录列表传递给DataFrame构造函数,而不是将记录迭代的附加到构造函数来创建。

join

SQL风格的合并

left = pd.DataFrame({'key': ['foo', 'bar'], 'lval': [1, 2]})

right = pd.DataFrame({'key': ['foo', 'bar'], 'rval': [4, 5]})

left

    key     lval
0   foo     1
1   bar     2

right

    key     rval
0   foo     4
1   bar     5

pd.merge(left, right, on='key')

    key     lval    rval
0   foo     1       4
1   bar     2       5

append

追加行

df = pd.DataFrame(np.random.randn(8, 4), columns=['A', 'B', 'C', 'D'])

df

    A           B           C           D
0   -0.860211   -0.681749   0.152113    1.829671
1   0.004854    0.937729    0.365849    0.392581
2   -1.751431   -1.163461   -1.424000   -0.627213
3   0.968618    0.301468    -0.571927   0.479373
4   0.208289    -1.038097   -0.411260   -0.649550
5   -1.077596   -1.331363   -0.582295   -1.155106
6   0.158382    0.184384    -0.278690   0.228828
7   0.325416    -0.337622   0.109289    0.052013

s = df.iloc[3]

df.append(s)

    A           B           C           D
0   -0.860211   -0.681749   0.152113    1.829671
1   0.004854    0.937729    0.365849    0.392581
2   -1.751431   -1.163461   -1.424000   -0.627213
3   0.968618    0.301468    -0.571927   0.479373
4   0.208289    -1.038097   -0.411260   -0.649550
5   -1.077596   -1.331363   -0.582295   -1.155106
6   0.158382    0.184384    -0.278690   0.228828
7   0.325416    -0.337622   0.109289    0.052013
3   0.968618    0.301468    -0.571927   0.479373
3.6 分组

分组通常涉及以下一个或多个步骤

  • 拆分数据到基于某项标准的组

  • 将功能独立应用于每个组

  • 将结果合并为数据结构

df = pd.DataFrame({'A': ['foo', 'bar', 'foo', 'bar','foo', 'bar', 'foo', 'foo'],
                    'B': ['one', 'one', 'two', 'three','two', 'two', 'one', 'three'],
                    'C': np.random.randn(8),
                    'D': np.random.randn(8)})

df

    A       B       C           D
0   foo     one     -0.975373   -0.806392
1   bar     one     0.308754    0.717588
2   foo     two     0.401024    -0.518380
3   bar     three   -0.133624   -0.807165
4   foo     two     -0.328228   -0.902858
5   bar     two     0.130461    -0.869063
6   foo     one     0.015876    -1.470854
7   foo     three   -0.866311   0.018694

按照A列分组,将sum()应用于结果组

df.groupby(by='A').sum()

    C           D
A
bar 0.305591    -0.958640
foo -1.753011   -3.679789

多列分组后,生成多层索引,也可以应用sum()函数

df.groupby(by=['A','B']).sum()


            C           D
A   B
bar one     0.308754    0.717588
    three   -0.133624   -0.807165
    two     0.130461    -0.869063
foo one     -0.959497   -2.277246
    three   -0.866311   0.018694
    two     0.072797    -1.421238
3.7 重塑

堆叠(Stack)

tuples = list(zip(*[['bar', 'bar', 'baz', 'baz',
                     'foo', 'foo', 'qux', 'qux'],
                    ['one', 'two', 'one', 'two',
                     'one', 'two', 'one', 'two']]))

index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])

df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=['A', 'B'])

df2 = df[:4]

df2

                A           B
first   second
bar     one     0.783239    0.213573
        two     -0.873571   -0.063300
baz     one     -1.717813   -0.930024
        two     0.857159    0.624150

stack()方法压缩DataFrame列中的级别

df3 = df2.stack()

df3

first  second
bar    one     A    0.783239
               B    0.213573
       two     A   -0.873571
               B   -0.063300
baz    one     A   -1.717813
               B   -0.930024
       two     A    0.857159
               B    0.624150
dtype: float64

df3.index

MultiIndex(levels=[['bar', 'baz', 'foo', 'qux'], ['one', 'two'], ['A', 'B']],
           labels=[[0, 0, 0, 0, 1, 1, 1, 1], [0, 0, 1, 1, 0, 0, 1, 1], [0, 1, 0, 1, 0, 1, 0, 1]],
           names=['first', 'second', None])

stack()的逆运算是unstack()

df3.unstack(level=0)

        first   bar         baz
second
one     A       0.783239    -1.717813
        B       0.213573    -0.930024
two     A       -0.873571   0.857159
        B       -0.063300   0.624150
3.8 数据透视表
df = pd.DataFrame({'A': ['one', 'one', 'two', 'three'] * 3,
                    'B': ['A', 'B', 'C'] * 4,
                    'C': ['foo', 'foo', 'foo', 'bar', 'bar', 'bar'] * 2,
                    'D': np.random.randn(12),
                    'E': np.random.randn(12)})

df

    A       B   C       D           E
0   one     A   foo     -0.090517   -0.666279
1   one     B   foo     0.264054    -0.443162
2   two     C   foo     -0.688052   0.306421
3   three   A   bar     -0.256553   0.532103
4   one     B   bar     0.011608    -0.651829
5   one     C   bar     0.626846    0.253946
6   two     A   foo     -0.315648   0.723746
7   three   B   foo     2.186395    0.127881
8   one     C   foo     -0.581125   0.053616
9   one     A   bar     -1.525911   0.639287
10  two     B   bar     0.625725    -1.012750
11  three   C   bar     1.701070    1.144568

创建数据透视表

df.pivot_table(index=['A','C'], values='E')

                E
A       C
one     bar     0.080468
        foo     -0.351941
three   bar     0.838335
        foo     0.127881
two     bar     -1.012750
        foo     0.515084
3.9 时间序列

Pandas提供了简单、强大、高效的功能,可以在频率转换过程中执行重采样操作。

rng = pd.date_range('1/1/2012', periods=100, freq='S')

ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)

ts.resample('10s').sum()

2012-01-01 00:00:00    2695
2012-01-01 00:00:10    2165
2012-01-01 00:00:20    2352
2012-01-01 00:00:30    2772
2012-01-01 00:00:40    1976
2012-01-01 00:00:50    2296
2012-01-01 00:01:00    2647
2012-01-01 00:01:10    2633
2012-01-01 00:01:20    2772
2012-01-01 00:01:30    1915
Freq: 10S, dtype: int64

时区表示

rng = pd.date_range('10/1/2012 00:00', periods=5, freq='D')

ts = pd.Series(np.random.randn(len(rng)), rng)

ts

2012-10-01   -0.815272
2012-10-02   -0.155452
2012-10-03    0.746936
2012-10-04   -0.183100
2012-10-05    0.294586
Freq: D, dtype: float64


ts_utc = ts.tz_localize('UTC')

ts_utc

2012-10-01 00:00:00+00:00   -0.815272
2012-10-02 00:00:00+00:00   -0.155452
2012-10-03 00:00:00+00:00    0.746936
2012-10-04 00:00:00+00:00   -0.183100
2012-10-05 00:00:00+00:00    0.294586
Freq: D, dtype: float64

时区转换

ts_utc.tz_convert('US/Eastern')

2012-09-30 20:00:00-04:00   -0.815272
2012-10-01 20:00:00-04:00   -0.155452
2012-10-02 20:00:00-04:00    0.746936
2012-10-03 20:00:00-04:00   -0.183100
2012-10-04 20:00:00-04:00    0.294586
Freq: D, dtype: float64

时间段转换

rng = pd.date_range('1/1/2020', periods=5, freq='M')

ts = pd.Series(np.random.randn(len(rng)), index=rng)

ts

2020-01-31    1.358113
2020-02-29    1.446364
2020-03-31    0.166628
2020-04-30   -0.487859
2020-05-31    2.055487
Freq: M, dtype: float64

ps = ts.to_period()

ps

2020-01    1.358113
2020-02    1.446364
2020-03    0.166628
2020-04   -0.487859
2020-05    2.055487
Freq: M, dtype: float64

ps.index

PeriodIndex(['2020-01', '2020-02', '2020-03', '2020-04', '2020-05'], dtype='period[M]', freq='M')
ts.to_timestamp()

ps.to_timestamp()

2020-01-01    1.358113
2020-02-01    1.446364
2020-03-01    0.166628
2020-04-01   -0.487859
2020-05-01    2.055487
Freq: MS, dtype: float64

在周期和时间戳之间转换可以使用一些方便的算术函数。在以下示例中,我们将以11月结束的年度的季度频率转换为季度结束后的月末的上午9点

prng = pd.period_range('2009Q1', '2020Q4', freq='Q-NOV')

ts = pd.Series(np.random.randn(len(prng)), prng)

ts.index = (prng.asfreq('M', 'e') + 1).asfreq('H', 's') + 9

ts.head()

2009-03-01 09:00    1.119304
2009-06-01 09:00   -0.609900
2009-09-01 09:00   -0.274391
2009-12-01 09:00    0.821375
2010-03-01 09:00    0.312619
Freq: H, dtype: float64
3.10 分类

Pandas 的 DataFrame 里可以包含类别数据

df = pd.DataFrame({"id": [1, 2, 3, 4, 5, 6],
                   "raw_grade": ['a', 'b', 'b', 'a', 'a', 'e']})

df

    id  raw_grade
0   1   a
1   2   b
2   3   b
3   4   a
4   5   a
5   6   e

将原始成绩转换为分类数据类型

df = pd.DataFrame({"id": [100, 60, 75, 80, 90, 85],
                   "raw_grade": ['a', 'c', 'b', 'b', 'a', 'a']})

df

    id      raw_grade
0   100     a
1   60      c
2   75      b
3   80      b
4   90      a
5   85      a

df['grade'] = df['raw_grade'].astype('category')

df['grade'].cat.categories

Index(['a', 'b', 'c'], dtype='object')

df['grade'].cat.categories=['优秀','良好','合格']

df

    id      raw_grade   grade
0   100     a           优秀
1   60      c           合格
2   75      b           良好
3   80      b           良好
4   90      a           优秀
5   85      a           优秀
3.11 数据可视化

DataFrame的plot()方法可以快速绘制带有标签的所有列

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
%matplotlib inline

ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2020', periods=1000))

df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index, columns=['A', 'B', 'C', 'D'])

df = df.cumsum()

plt.figure(figsize=(100,60),dpi=100)

df.plot()

plt.legend(loc='upper left')
img1.png

img1.png

4. 层次化索引

层次化索引是Pandas的一项重要功能,可以在一个轴上拥有多个(两个以上)索引级别。它使得我们能以低纬度的形式处理高纬度的数据。

4.1 创建一个MultiIndex(分层索引)对象

使用元组数组创建

arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
         ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]

tuples = list(zip(*arrays))

tuples

[('bar', 'one'),
 ('bar', 'two'),
 ('baz', 'one'),
 ('baz', 'two'),
 ('foo', 'one'),
 ('foo', 'two'),
 ('qux', 'one'),
 ('qux', 'two')]

index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])

index

MultiIndex(levels=[['bar', 'baz', 'foo', 'qux'], ['one', 'two']],
           labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
           names=['first', 'second'])

s = pd.Series(np.random.randn(8), index=index)

s

       first    second
bar    one      -0.005092
       two      -0.674584
baz    one      -0.653997
       two      -1.407524
foo    one       0.540062
       two      -1.876460
qux    one      -0.134661
       two       1.240625
dtype: float64

如果需要可迭代元素中的每个元素配对,可以使用MultiIndex.from_product()

iterables = [['bar', 'baz', 'foo', 'qux'], ['one', 'two']]

pd.MultiIndex.from_product(iterables, names=['first', 'second'])


MultiIndex(levels=[['bar', 'baz', 'foo', 'qux'], ['one', 'two']],
           labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
           names=['first', 'second'])

为了方便起见,可以将数组列表直接传递给Series或Dataframe构造一个MultiIndex

arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
         ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]

pd.DataFrame(np.random.randn(8,4), index=arrays)

        0           1           2           3
bar one -1.622456   0.636925    1.282333    0.687830
    two -0.002939   -0.732816   -1.208273   2.155731
baz one 1.433688    -0.442555   -1.822969   0.290839
    two 0.128731    -0.039224   -0.338896   -0.276191
foo one 0.425498    0.126022    0.410600    0.420223
    two 0.809227    -0.203693   0.510678    0.573741
qux one -0.306412   -1.624998   -0.701514   0.736233
    two 1.284330    -2.710565   -1.951096   -0.508593
4.2 重建level

使用get_level_values()方法可以查看特定级别上的标签

index

MultiIndex(levels=[['bar', 'baz', 'foo', 'qux'], ['one', 'two']],
           labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
           names=['first', 'second'])

index.get_level_values(0)

Index(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'], dtype='object', name='first')

index.get_level_values('second')

Index(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'], dtype='object', name='second')

使用MultiIndex在轴上索引数据

层级索引的一个重要功能是,可以使用“部分”标签选择数据,以标识数据中的子组。

df

first   bar                     foo
second  one         two         one         two
A       -0.732161   0.546570    -0.497862   -0.031316
B       -2.929698   0.220268    -0.991737   -0.666499
C       -1.474020   -0.346027   -1.702726   -0.275031

df['bar']

second  one         two
A       -0.732161   0.546570
B       -2.929698   0.220268
C       -1.474020   -0.346027

df['bar','one']

A   -0.732161
B   -2.929698
C   -1.474020
Name: (bar, one), dtype: float64


df['bar']['one']

A   -0.732161
B   -2.929698
C   -1.474020
Name: one, dtype: float64

定义级别

MultiIndex保持一个索引的所有定义级别,即使没有被实际使用

df.columns.levels

FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']])

df[['bar']].columns.levels

FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']])

这样做是为了避免重新计算级别,以使切片性能更高。如果只想查看使用的级别,则可以使用get_level_values()方法

df[['bar']].columns.get_level_values(0)

Index(['bar', 'bar'], dtype='object', name='first')
4.3 索引切片

MultiIndex也可以使用.loc达到我们期望的效果

df


                A           B           C
first   second
bar     one     -0.732161   -2.929698   -1.474020
        two     0.546570    0.220268    -0.346027
foo     one     -0.497862   -0.991737   -1.702726
        two     -0.031316   -0.666499   -0.275031

df.loc[('bar', 'two')]

A    0.546570
B    0.220268
C   -0.346027
Name: (bar, two), dtype: float64

注意:这里是可以使用df.loc['bar', 'two']这样的简写形式的,但是,可能会出现歧义

索引还想使用.loc索引特定的列,就必须使用元组,而不是简写形式

df.loc[('bar','two'),'A']

0.546570

使用“部分”索引获取这一级别的所有元素,当然这是df.loc[('bar',),:]的简写

df.loc['bar']

        A           B           C
second
one     -0.732161   -2.929698   -1.474020
two     0.546570    0.220268    -0.346027

您可以通过提供一个元组切片来对值的“范围”进行切片

df.loc[('bar','one'):('foo','one'),]

                A           B           C
first   second
bar     one     -0.732161   -2.929698   -1.474020
        two     0.546570    0.220268    -0.346027
foo     one     -0.497862   -0.991737   -1.702726

传递标签或元组列表的工作方式与重新编制索引相似

df.loc[[('bar','one'),('foo','one')]]

                A           B           C
first   second
bar     one     -0.732161   -2.929698   -1.474020
foo     one     -0.497862   -0.991737   -1.702726

一个元组列表索引了几个完整的MultiIndex键,而一个列表元组则引用了一个级别中的多个值

s = pd.Series([1, 2, 3, 4, 5, 6], index=pd.MultiIndex.from_product([["A", "B"], ["c", "d", "e"]]))

s

A  c    1
   d    2
   e    3
B  c    4
   d    5
   e    6
dtype: int64


s.loc[[("A", "c"), ("B", "d")]]  # list of tuples

A  c    1
B  d    5
dtype: int64


s.loc[(["A", "B"], ["c", "d"])]  # tuple of lists

A  c    1
   d    2
B  c    4
   d    5
dtype: int64
1. 使用slice()
 def mklbl(prefix, n):
    return ["%s%s" % (prefix, i) for i in range(n)]

miindex = pd.MultiIndex.from_product([mklbl('A', 3),
                                      mklbl('B', 2),
                                      mklbl('C', 3),
                                      mklbl('D', 1)])

micolumns = pd.MultiIndex.from_tuples([('a', 'foo'), ('a', 'bar'),
                                       ('b', 'foo'), ('b', 'bah')],
                                      names=['lvl0', 'lvl1'])

dfmi = pd.DataFrame(np.arange(len(miindex) * len(micolumns)).reshape((len(miindex), len(micolumns))),index=miindex, columns=micolumns).sort_index().sort_index(axis=1)

dfmi

            lvl0    a       b
            lvl1    bar foo bah foo
A0  B0  C0  D0      1   0   3   2
        C1  D0      5   4   7   6
        C2  D0      9   8   11  10
    B1  C0  D0      13  12  15  14
        C1  D0      17  16  19  18
        C2  D0      21  20  23  22
A1  B0  C0  D0      25  24  27  26
        C1  D0      29  28  31  30
        C2  D0      33  32  35  34
    B1  C0  D0      37  36  39  38
        C1  D0      41  40  43  42
        C2  D0      45  44  47  46
A2  B0  C0  D0      49  48  51  50
        C1  D0      53  52  55  54
        C2  D0      57  56  59  58
    B1  C0  D0      61  60  63  62
        C1  D0      65  64  67  66
        C2  D0      69  68  71  70

使用slice()完成基本的切片功能

dfmi.loc[(slice('A0','A1'),slice(None),slice('C0','C1')),:]

            lvl0    a       b
            lvl1    bar foo bah foo
A0  B0  C0  D0      1   0   3   2
        C1  D0      5   4   7   6
    B1  C0  D0      13  12  15  14
        C1  D0      17  16  19  18
A1  B0  C0  D0      25  24  27  26
        C1  D0      29  28  31  30
    B1  C0  D0      37  36  39  38
        C1  D0      41  40  43  42

也可以使用 pandas.IndexSlice,是语法看起来更自然,实现上述相同的效果

idx = pd.IndexSlice

dfmi.loc[idx['A0':'A1', :, 'C0':'C1'],:]

使用此方法可以在多个轴上同时执行非常复杂的选择

dfmi.loc[idx['A1', 'B1', 'C1':'C2'],idx[:,'foo']]

            lvl0    a   b
            lvl1    foo foo
A1  B1  C1  D0      40  42
        C2  D0      44  46

使用布尔索引器,可以提供与值相关的选择

dfmi.loc[idx[dfmi[('a','foo')]>30,:,'C1':'C2'],idx[:,'foo']]

            lvl0    a   b
            lvl1    foo foo
A1  B0  C2  D0      32  34
    B1  C1  D0      40  42
        C2  D0      44  46
A2  B0  C1  D0      52  54
        C2  D0      56  58
    B1  C1  D0      64  66
        C2  D0      68  70

还可以指定单轴传递给切片器,切片器只在0轴传递

dfmi.loc(axis=0)[:,'B1','C0':'C1']

            lvl0    a       b
            lvl1    bar foo bah foo
A0  B1  C0  D0      13  12  15  14
        C1  D0      17  16  19  18
A1  B1  C0  D0      37  36  39  38
        C1  D0      41  40  43  42
A2  B1  C0  D0      61  60  63  62
        C1  D0      65  64  67  66

当然,也可以使用这种方法设置值

dfmi.loc(axis=0)[:,'B1','C1'] = 0

            lvl0    a   b
            lvl1    bar foo bah foo
A0  B0  C0  D0      1   0   3   2
        C1  D0      5   4   7   6
        C2  D0      9   8   11  10
    B1  C0  D0      13  12  15  14
        C1  D0      0   0   0   0
        C2  D0      21  20  23  22
A1  B0  C0  D0      25  24  27  26
        C1  D0      29  28  31  30
        C2  D0      33  32  35  34
    B1  C0  D0      37  36  39  38
        C1  D0      0   0   0   0
        C2  D0      45  44  47  46
A2  B0  C0  D0      49  48  51  50
        C1  D0      53  52  55  54
        C2  D0      57  56  59  58
    B1  C0  D0      61  60  63  62
        C1  D0      0   0   0   0
        C2  D0      69  68  71  70
2. 特定级别筛选

使用xs()方法,可以在特定级别上筛选数据

df

                    A           B           C
first   second
bar     one         -0.732161   -2.929698   -1.474020
        two         0.546570    0.220268    -0.346027
foo     one         -0.497862   -0.991737   -1.702726
        two         -0.031316   -0.666499   -0.275031

df.xs('one', level='second')

            A           B           C
first
bar         -0.732161   -2.929698   -1.474020
foo         -0.497862   -0.991737   -1.702726

等效的slice()方法是

df.loc[(slice(None),'one'),:]

xs()通过指定参数,也可以在列上选择

df.T.xs('two', level='second',axis=1)

first   bar         foo
A       0.546570    -0.031316
B       0.220268    -0.666499
C       -0.346027   -0.275031

等效的slice()方法是

df.T.loc[:,(slice(None),'two')]

xs()还允许使用多个键进行选择

df.T.xs(('one','foo'),level=('second','first'),axis=1)

first   foo
second  one
A       -0.497862
B       -0.991737
C       -1.702726

等效的slice()方法是

df.T.loc[:,('foo','one')]

可以传递drop_level=False保留所选的级别

df.xs('one',level='second',drop_level=False)

                    A           B           C
first   second
bar     one         -0.732161   -2.929698   -1.474020
foo     one         -0.497862   -0.991737   -1.702726

可以看到level为second的级别被保留下来,比较一下

df.xs('one',level='second',drop_level=True)

        A           B           C
first
bar     -0.732161   -2.929698   -1.474020
foo     -0.497862   -0.991737   -1.702726
3. 重建索引和对齐

在方法 reindex() and align()中使用level参数,控制跨级别的广播

midx = pd.MultiIndex(levels=[['zero', 'one'], ['x', 'y']], labels=[[1, 1, 0, 0], [1, 0, 1, 0]],names=['level1','level2'])

df = pd.DataFrame(np.random.randn(4, 2), index=midx)

df

                0       1
level1  level2
one     y       0.169759    1.831895
        x       0.891265    0.515718
zero    y       -1.698754   0.869791
        x       0.449925    -0.585147

df2 = df.mean(level='level2')

df2

        0           1
level2
y       -0.764498   1.350843
x       0.670595    -0.034715


df2 = df2.reindex(df.index, level='level2')

df2
                0           1
level1  level2
one     y       -0.764498   1.350843
        x       0.670595    -0.034715
zero    y       -0.764498   1.350843
        x       0.670595    -0.034715

使用align()对齐

df_aligned, df2_aligned = df.align(df2, level=0)

df_aligned

                0           1
level1  level2
one     y       0.169759    1.831895
        x       0.891265    0.515718
zero    y       -1.698754   0.869791
        x       0.449925    -0.585147

df2_aligned

                0           1
level1  level2
one     y       -0.764498   1.350843
        x       0.670595    -0.034715
zero    y       -0.764498   1.350843
        x       0.670595    -0.034715
4. 用swaplevel()交换级别
df

                0           1
level1  level2
one     y       0.169759    1.831895
        x       0.891265    0.515718
zero    y       -1.698754   0.869791
        x       0.449925    -0.585147

df.swaplevel(1,0,axis=0)

                0           1
level2  level1
y       one     0.169759    1.831895
x       one     0.891265    0.515718
y       zero    -1.698754   0.869791
x       zero    0.449925    -0.585147
5. 使用reorder_levels()重新排序

reorder_levels()方法对swaplevel()方法进行了概括,可以一步一步地排列层次结构索引级别

df.reorder_levels([1,0],axis=0)

                0           1
level2  level1
y       one     0.169759    1.831895
x       one     0.891265    0.515718
y       zero    -1.698754   0.869791
x       zero    0.449925    -0.585147
6. 重命名索引

使用rename()方法可以重命名行和列

df.rename(columns={0:'A',1:'B'})

                A           B
level1  level2
one     y       0.169759    1.831895
        x       0.891265    0.515718
zero    y       -1.698754   0.869791
        x       0.449925    -0.585147

索引重命名

df.rename(index={'one':'ONE','y':'Y'})

                0           1
level1  level2
ONE     Y       0.169759    1.831895
        x       0.891265    0.515718
zero    Y       -1.698754   0.869791
        x       0.449925    -0.585147

reset_index()用于将索引移动到列中

df.reset_index(level='level2')

            level2      0           1
level1
one         y           0.169759    1.831895
one         x           0.891265    0.515718
zero        y           -1.698754   0.869791
zero        x           0.449925    -0.585147
4.4. 排序

层次化索引同样可以使用sort_index()排序

import random

tuples = [('foo', 'two'), ('bar', 'two'),('baz', 'one'),('bar', 'one'),('qux', 'two'),('baz', 'two'),('qux', 'one'),('foo', 'one')]

random.shuffle(tuples)

s = pd.Series(np.random.randn(8), index=pd.MultiIndex.from_tuples(tuples))

s.index.set_names(['L1','L2'], inplace=True)

s

L1   L2
qux  one    2.024294
bar  two    0.704686
baz  one    0.698701
foo  two    0.456529
qux  two    0.252748
bar  one    0.995226
baz  two    1.246236
foo  one    0.102926
dtype: float64

s.sort_index(level=1)

L1   L2
bar  one    0.995226
baz  one    0.698701
foo  one    0.102926
qux  one    2.024294
bar  two    0.704686
baz  two    1.246236
foo  two    0.456529
qux  two    0.252748
dtype: float64

也可以使用级别名称排序

s.sort_index(level='L1')

L1   L2
bar  one    0.995226
     two    0.704686
baz  one    0.698701
     two    1.246236
foo  one    0.102926
     two    0.456529
qux  one    2.024294
     two    0.252748
dtype: float64

注意:在索引数据时,即使没有对索引排序,也可以正常工作,只是效率很低。它将返回数据的副本,而不是视图

dfm = pd.DataFrame({'jim': [0, 0, 1, 1],'joe': ['x', 'x', 'z', 'y'],'jolie': np.random.rand(4)})

dfm.set_index(['jim', 'joe'],inplace=True)

dfm

        jolie
jim joe
0   x   0.611873
    x   0.636060
1   z   0.871884
    y   0.745177

dfm.loc[(1,'z')]

        jolie
jim joe
1   z   0.871884

可以正常查找到数据,但是会看到这样的警告 PerformanceWarning: indexing past lexsort depth may impact performance.

如果,对没有排序的数据切片索引的话,会报错UnsortedIndexError: 'Key length (2) was greater than MultiIndex lexsort depth (1)'

dfm.loc[(0,'x'):(1,'z')]

排序后在执行,就不会出错了

dfm.sort_index(inplace=True)

dfm.loc[(0,'x'):(1,'z')]

        jolie
jim joe
0   x   0.325346
    x   0.878943
1   y   0.178959
    z   0.793376

可以使用is_lexsorted()方法查看索引是否已排序

dfm.index.is_lexsorted()

True
4.5 索引类型
1. TimedeltaIndex
2. DatetimeIndex/Timestamps
3. CategoricalIndex
4. Int64Index
5. RangeIndex
6. Float64Index
7. IntervalIndex

5. IO操作

参考网站:https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html

Format Type

Data Description

Reader

Writer

text

CSV

read_csv

to_csv

text

Fixed-Width Text File

read_fwf

text

JSON

read_json

to_json

text

HTML

read_html

to_html

text

Local clipboard

read_clipboard

to_clipboard

MS Excel

read_excel

to_excel

binary

OpenDocument

read_excel

binary

HDF5 Format

read_hdf

to_hdf

binary

Feather Format

read_feather

to_feather

binary

Parquet Format

read_parquet

to_parquet

binary

ORC Format

read_orc

binary

Msgpack

read_msgpack

to_msgpack

binary

Stata

read_stata

to_stata

binary

SAS

read_sas

binary

SPSS

read_spss

binary

Python Pickle Format

read_pickle

to_pickle

SQL

SQL

read_sql

to_sql

SQL

Google BigQuery

read_gbq

to_gbq

5.1 CSV文件和文本文件

read_csv()方法常用参数:

  • filepath_or_buffer :文件的路径、URL或带有read()方法的任何对象

  • sep:定界符,默认为,支持正则表达式

  • delimiter: 定界符,sep的备用参数名

  • delim_whitespace:指定是否将空格作为分隔符,等效于sep='\s+'

  • header:行号(用作列名)以及数据的开头,默认行为是推断列名

  • names:指定列名

  • index_col:用作行标签的列

  • usecols:返回列的子集。使用此参数可以大大加快解析时间并降低内存使用量。

  • squeeze:如果解析的数据仅包含一列,则返回Series

  • prefix:无标题时添加到列号的前缀,例如X0,X1

  • dtype:指定数据或列的数据类型

  • engine:选择要使用的解析引擎。C引擎速度更快,而Python引擎当前功能更完善

  • skipinitialspace:在定界符后忽略空格

  • skiprows:在文件开始处要跳过的行号或要跳过的行数

  • skipfooter:在文件底部要跳过的行数

  • nrows:要读取的文件行数。在读取大文件时非常有用

  • memory_map:默认值True,在内部对文件进行分块处理,从而在解析时减少了内存使用,但可能是混合类型推断。为确保没有混合类型,请设置False,或使用dtype参数指定类型。注意:无论将整个文件读取为单个文件,都可以使用chunksize或iterator参数以块形式返回数据。(仅对C解析器有效)

  • memory_map:如果提供了filepath_or_buffer文件路径,则将文件对象直接映射到内存中,然后直接从那里访问数据。使用此选项可以提高性能,因为不再有任何I/O开销。

  • parse_dates:解析日期列

  • date_parser:用于将字符串列序列转换为日期时间实例数组的函数

  • iterator:返回TextFileReader对象以进行迭代或使用get_chunk()获取块

  • chunksize:返回TextFileReader对象以进行迭代

  • compression:对磁盘数据进行即时解压缩

  • thousands:千位符

  • encoding:编码方式

  • error_bad_lines:出错行处理方式

6. 合并、连接

Pandas提供了concat、append、join和merge四种方法用于dataframe的拼接,其大致特点和区别如下:

方法

解释

.concat()

pandas的顶级方法,提供了axis设置可用于df间行方向(增加行,下同)或列方向(增加列,下同)进行内联或外联拼接操作

.append()

dataframe数据类型的方法,提供了行方向的拼接操作

.join()

dataframe数据类型的方法,提供了列方向的拼接操作,支持左联、右联、内联和外联四种操作类型

.merge()

pandas的顶级方法,提供了类似于SQL数据库连接操作的功能,支持左联、右联、内联和外联等全部四种SQL连接操作类型

6.1 pd.concat()
concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False,
           keys=None, levels=None, names=None, verify_integrity=False,
           copy=True)
"""
常用参数说明:
axis:拼接轴方向,默认为0,沿行拼接;若为1,沿列拼接
join:默认外联'outer',拼接另一轴所有的label,缺失值用NaN填充;内联'inner',只拼接另一轴相同的label;
join_axes: 指定需要拼接的轴的labels,可在join既不内联又不外联的时候使用
ignore_index:对index进行重新排序
keys:多重索引
"""
6.2 pd.append()

注意:效率很低(因为要创建一个新的对象)

append(self, other, ignore_index=False, verify_integrity=False)
"""
常用参数说明:
other:另一个df
ignore_index:若为True,则对index进行重排
verify_integrity:对index的唯一性进行验证,若有重复,报错。若已经设置了ignore_index,则该参数无效
"""
6.3 pd.join()
join(other, on=None, how='left', lsuffix='', rsuffix='', sort=False)
"""
常用参数说明:
on:参照的左边df列名key(可能需要先进行set_index操作),若未指明,按照index进行join
how:{‘left’, ‘right’, ‘outer’, ‘inner’}, 默认‘left’,即按照左边df的index(若声明了on,则按照对应的列);若为‘right’abs照左边的df
    若‘inner’为内联方式;若为‘outer’为全连联方式。
sort:是否按照join的key对应的值大小进行排序,默认False
lsuffix,rsuffix:当left和right两个df的列名出现冲突时候,通过设定后缀的方式避免错误
"""
6.4 pd.merge()
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None,
          left_index=False, right_index=False, sort=False,
          suffixes=('_x', '_y'), copy=True, indicator=False,
          validate=None):
"""
既可作为pandas的顶级方法使用,也可作为DataFrame数据结构的方法进行调用
常用参数说明:
how:{'left’, ‘right’, ‘outer’, ‘inner’}, 默认‘inner’,类似于SQL的内联。'left’类似于SQL的左联;'right’类似于SQL的右联;
    ‘outer’类似于SQL的全联。
on:进行合并的参照列名,必须一样。若为None,方法会自动匹配两张表中相同的列名
left_on: 左边df进行连接的列
right_on: 右边df进行连接的列
suffixes: 左、右列名称前缀
validate:默认None,可定义为“one_to_one” 、“one_to_many” 、“many_to_one”和“many_to_many”,即验证是否一对一、一对多、多对一或
    多对多关系
"""
"""
SQL语句复习:
内联:SELECT a.*, b.* from table1 as a inner join table2 as b on a.ID=b.ID
左联:SELECT a.*, b.* from table1 as a left join table2 as b on a.ID=b.ID
右联:SELECT a.*, b.* from table1 as a right join table2 as b on a.ID=b.ID
全联:SELECT a.*, b.* from table1 as a full join table2 as b on a.ID=b.ID
"""

7. 重塑和数据透视表

7.1 堆叠和卸堆

stack()unstack()可以在SeriesDataFrame上使用。这些方法旨在与MultiIndex对象一起使用,可以这样简单理解:

  • stack:将数据的列旋转为行

  • unstack:将数据的行旋转为列

  • stack和unstack默认操作为最内层

  • stack和unstack默认旋转轴的级别将会成为结果中的最低级别(最内层)

  • stack和unstack为一组逆运算操作

tuples = list(zip(*[['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
                    ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]))

index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])

df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=['A', 'B'])

df

                A           B
first   second
bar     one     0.291462    -0.586688
        two     -1.102377   1.166191
baz     one     0.377912    -0.272142
        two     -0.061330   -0.869745
foo     one     0.178222    -0.794190
        two     0.868143    -0.146990
qux     one     -0.416080   1.950550
        two     -0.153945   0.973521

来看一下 stack()堆叠操作

stacked = df.stack()

stacked

first  second
bar    one     A    0.291462
               B   -0.586688
       two     A   -1.102377
               B    1.166191
baz    one     A    0.377912
               B   -0.272142
       two     A   -0.061330
               B   -0.869745
foo    one     A    0.178222
               B   -0.794190
       two     A    0.868143
               B   -0.146990
qux    one     A   -0.416080
               B    1.950550
       two     A   -0.153945
               B    0.973521
dtype: float64

可以看到,最内层的行被旋转成列,并且成为结果中的最低级别(最内层), unstack()可以执行相反的操作

stacked.unstack()

                A           B
first   second
bar     one     0.291462    -0.586688
        two     -1.102377   1.166191
baz     one     0.377912    -0.272142
        two     -0.061330   -0.869745
foo     one     0.178222    -0.794190
        two     0.868143    -0.146990
qux     one     -0.416080   1.950550
        two     -0.153945   0.973521

可以指定level对特定级别卸堆

stacked.unstack(level='second')

        second  one         two
first
bar     A       0.291462    -1.102377
        B       -0.586688   1.166191
baz     A       0.377912    -0.061330
        B       -0.272142   -0.869745
foo     A       0.178222    0.868143
        B       -0.794190   -0.146990
qux     A       -0.416080   -0.153945
        B       1.950550    0.973521

一次处理一个以上的级别

stacked.unstack(level=['first','second'])

first   bar                     baz                     foo                     qux
second  one         two         one         two         one         two         one         two
A       0.291462    -1.102377   0.377912    -0.061330   0.178222    0.868143    -0.41608    -0.153945
B       -0.586688   1.166191    -0.272142   -0.869745   -0.794190   -0.146990   1.95055     0.973521
7.2 熔化重塑

melt()有点像用Excel做透视和逆透视的过程

pandas.melt(frame, id_vars=None, value_vars=None, var_name=None, value_name='value', col_level=None)

"""
参数解释:

frame:要处理的数据集。

id_vars:不需要被转换的列名。

value_vars:需要转换的列名,如果剩下的列全部都要转换,就不用写了。

var_name和value_name是自定义设置对应的列名。

col_level :如果列是MultiIndex,则使用此级别。
"""

看一个官方的例子

df = pd.DataFrame({'A': {0: 'a', 1: 'b', 2: 'c'},
                'B': {0: 1, 1: 3, 2: 5},
                'C': {0: 2, 1: 4, 2: 6}})

df

    A   B   C
0   a   1   2
1   b   3   4
2   c   5   6

df.melt(id_vars=['A'], value_vars=['B','C'])

        A           variable    value
0       a           B           1
1       b           B           3
2       c           B           5
3       a           C           2
4       b           C           4
5       c           C           6

多层索引的例子

df.columns = [list('ABC'), list('DEF')]

df

    A   B   C
    D   E   F
0   a   1   2
1   b   3   4
2   c   5   6

df.melt(col_level=0, id_vars=['A'], value_vars=['B'])

    A       variable    value
0   a       B           1
1   b       B           3
2   c       B           5
7.3 数据透视表

函数pivot_table()可用于创建电子表格样式的数据透视表

pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All')

"""
参数解释:

data:一个DataFrame对象。

values:要汇总的一列或一列列表。

index:与数据或它们的列表具有相同长度的列,Grouper,数组。在数据透视表索引上进行分组的键。如果传递了数组,则其使用方式与列值相同。

columns:与数据或它们的列表具有相同长度的列,Grouper,数组。在数据透视表列上进行分组的键。如果传递了数组,则其使用方式与列值相同。

margins:布尔值,默认值False,添加行/列边距(小计)

aggfunc:用于汇总的函数,默认为numpy.mean。
"""

看一个例子

import datetime

df = pd.DataFrame({'A': ['one', 'one', 'two', 'three'] * 3,
            'B': ['A', 'B', 'C'] * 4,
            'C': ['foo', 'foo', 'foo', 'bar', 'bar', 'bar'] * 2,
            'D': np.random.randn(12),
            'E': np.random.randn(12),
            'F': [datetime.datetime(2013, i, 1) for i in range(1, 7)] + [datetime.datetime(2013, i, 15) for i in range(1, 7)]})

df

    A       B   C       D           E           F
0   one     A   foo     -1.003470   -0.886539   2013-01-01
1   one     B   foo     -1.737911   0.577225    2013-02-01
2   two     C   foo     -2.265670   -2.073342   2013-03-01
3   three   A   bar     -0.725438   0.167703    2013-04-01
4   one     B   bar     2.437008    -1.473596   2013-05-01
5   one     C   bar     2.019172    1.904371    2013-06-01
6   two     A   foo     -2.533345   -0.040651   2013-01-15
7   three   B   foo     -1.962584   -0.397859   2013-02-15
8   one     C   foo     -0.397364   -0.208313   2013-03-15
9   one     A   bar     -0.411146   0.987424    2013-04-15
10  two     B   bar     0.809404    -0.207306   2013-05-15
11  three   C   bar     -0.891956   0.398404    2013-06-15

生成数据透视表

pd.pivot_table(df, index='A', columns=['B','C'], values='D',aggfunc=np.max)


B       A                       B                       C
C       bar         foo         bar         foo         bar         foo
   A
one     -0.411146   -1.003470   2.437008    -1.737911   2.019172    -0.397364
three   -0.725438   NaN         NaN         -1.962584   -0.891956   NaN
two     NaN         -2.533345   0.809404    NaN         NaN         -2.265670

indexcolumns关键字可以使用Grouper

pd.pivot_table(df, values='D', index=pd.Grouper(freq='M', key='F'),columns='C')

 C          bar         foo
    F
2013-01-31  NaN         -1.768407
2013-02-28  NaN         -1.850248
2013-03-31  NaN         -1.331517
2013-04-30  -0.568292   NaN
2013-05-31  1.623206    NaN
2013-06-30  0.563608    NaN

如果传递margins=True参数,会添加特殊的列和行All,并在行和列的类别之间添加部分组聚合

df.pivot_table(index=['A', 'B'], columns='C', aggfunc=np.sum, margins=True)

            D                                   E
        C   bar         foo         All         bar         foo         All
A       B
one     A   -0.411146   -1.003470   -1.414615   0.987424    -0.886539   0.100885
        B   2.437008    -1.737911   0.699097    -1.473596   0.577225    -0.896371
        C   2.019172    -0.397364   1.621808    1.904371    -0.208313   1.696058
three   A   -0.725438   NaN         -0.725438   0.167703    NaN         0.167703
        B   NaN         -1.962584   -1.962584   NaN         -0.397859   -0.397859
        C   -0.891956   NaN         -0.891956   0.398404    NaN         0.398404
two     A   NaN         -2.533345   -2.533345   NaN         -0.040651   -0.040651
        B   0.809404    NaN         0.809404    -0.207306   NaN         -0.207306
        C   NaN         -2.265670   -2.265670   NaN         -2.073342   -2.073342
All         3.237045    -9.900344   -6.663299   1.777000    -3.029479   -1.252478
7.4 交叉表

使用crosstab()计算两个(或更多)因素交叉列表。默认情况下crosstab,除非传递值数组和聚合函数,否则将计算因子的频率表

crosstab(index, columns, values=None, rownames=None, colnames=None, aggfunc=None, margins=False, margins_name='All', dropna=True, normalize=False)

"""
参数解释:

index:行上进行分组的键。

columns:列上进行分组的键。

values:要汇总的一列或一列列表。

aggfunc:函数,可选,如果未传递任何值数组,则计算频率表。

rownames:sequence,默认为None,必须与传递的行数组数匹配。

colnames:序列,默认值None,如果传递,则必须与传递的列数组数匹配。

margins:布尔值,默认值False,添加行/列边距(小计)

normalize:布尔值,{'all','index','columns'}或{0,1}(默认)False。通过将所有值除以值的总和进行归一化。

""""

样本数据

df = pd.DataFrame({'类别':['水果','水果','水果','蔬菜','蔬菜','肉类','肉类'],
                '产地':['美国','中国','中国','中国','新西兰','新西兰','美国'],
                '水果':['苹果','梨','草莓','番茄','黄瓜','羊肉','牛肉'],
               '数量':[5,5,9,3,2,10,8],
               '价格':[5,5,10,3,3,13,20]})

df

    类别  产地  水果  数量  价格
0   水果  美国  苹果  5   5
1   水果  中国      5  5
2   水果  中国  草莓  9   10
3   蔬菜  中国  番茄  3   3
4   蔬菜  新西兰 黄瓜  2   3
5   肉类  新西兰 羊肉  10  13
6   肉类  美国  牛肉  8   20

使用透视表,按照类别分组,统计各个分组中产地的频率

pd.pivot_table(df,index='类别',columns='产地',values='数量',aggfunc=np.size,fill_value=0)

产地  中国  新西兰 美国
类别
水果   2    0     1
肉类   0    1     1
蔬菜   1    1     0

使用交叉表(交叉表是透视表的一个特例)可以让这个过程更简单

pd.crosstab(df['类别'], df['产地'])

产地  中国  新西兰 美国
类别
水果   2    0     1
肉类   0    1     1
蔬菜   1    1     0

8. 文本数据

8.1 文本数据类型

在版本V1.0.0之后,Pandas中存储文本数据有两种方法:

  • object-dtype NumPy数组

  • StringDtype类型

通常建议使用StringDtype类型存储文本数据。虽然object是默认的类型,我们可以显示的指定dtype

pd.Series(['a', 'b', 'c'])

0    a
1    b
2    c
dtype: object

pd.Series(['a', 'b', 'c'], dtype='string')
# 等效于
pd.Series(['a', 'b', 'c'], dtype=pd.StringDtype())

0    a
1    b
2    c
dtype: string

或者使用astype

s= pd.Series(['a', 'b', 'c'])

s.astype('string')

Series和Index配备了一组字符串处理方法,这些方法可以轻松地对数组的每个元素进行操作。而且最重要的是,这些方法会自动排除缺失及NA值。这些可以通过.str属性访问,通常与内置字符串方法匹配

s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'], dtype="string")

s.str.upper()

0       A
1       B
2       C
3    AABA
4    BACA
5    <NA>
6    CABA
7     DOG
8     CAT
dtype: string

Index上的字符串方法对于清理或转换DataFrame列特别有用

df = pd.DataFrame(np.random.randn(3, 2),columns=[' Column A ', ' Column B '], index=range(3))

df

    Column A    Column B
0   -1.154830   1.732478
1   -0.450382   0.626425
2   -0.645320   1.827017

删除空格并转换成小写

df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_')

df

    column_a    column_b
0   -1.154830   1.732478
1   -0.450382   0.626425
2   -0.645320   1.827017
8.2 方法总结

具体参见:https://pandas.pydata.org/pandas-docs/stable/user_guide/text.html#method-summary

方法

描述

cat()

连接字符串

split()

在定界符上分割字符串

rsplit()

从字符串末尾在定界符上分割字符串

get()

索引到每个元素(检索第i个元素)

join()

使用传递的分隔符将字符串连接到系列的每个元素中

get_dummies()

在分隔符上分割字符串,返回虚拟变量的DataFrame

contains()

如果每个字符串包含模式/正则表达式,则返回布尔数组

replace()

将模式/正则表达式/字符串的出现替换为其他一些字符串或给定出现的可调用函数的返回值

repeat()

值重复(s.str.repeat(3)等于)x * 3

pad()

在字符串的左侧,右侧或两侧添加空格

center()

相当于 str.center

ljust()

相当于 str.ljust

rjust()

相当于 str.rjust

zfill()

相当于 str.zfill

wrap()

将长字符串拆分成长度小于给定宽度的行

slice()

将系列中的每个弦切成薄片

slice_replace()

用传递的值替换每个字符串中的切片

count()

计算模式的出现

startswith()

相当于str.startswith(pat)每个元素

endswith()

相当于str.endswith(pat)每个元素

findall()

计算每个字符串所有出现的模式/正则表达式的列表

match()

调用re.match每个元素,将匹配的组作为列表返回

extract()

调用re.search每个元素,返回DataFrame,其中每个元素一行,每个正则表达式捕获组一行

extractall()

调用re.findall每个元素,返回DataFrame,其中每个匹配项一行,每个正则表达式捕获组一行

len()

计算字符串长度

strip()

相当于 str.strip

rstrip()

相当于 str.rstrip

lstrip()

相当于 str.lstrip

partition()

相当于 str.partition

rpartition()

相当于 str.rpartition

lower()

相当于 str.lower

casefold()

相当于 str.casefold

upper()

相当于 str.upper

find()

相当于 str.find

rfind()

相当于 str.rfind

index()

相当于 str.index

rindex()

相当于 str.rindex

capitalize()

相当于 str.capitalize

swapcase()

相当于 str.swapcase

normalize()

返回Unicode普通格式。相当于unicodedata.normalize

translate()

相当于 str.translate

isalnum()

相当于 str.isalnum

isalpha()

相当于 str.isalpha

isdigit()

相当于 str.isdigit

isspace()

相当于 str.isspace

islower()

相当于 str.islower

isupper()

相当于 str.isupper

istitle()

相当于 str.istitle

isnumeric()

相当于 str.isnumeric

isdecimal()

相当于 str.isdecimal

9. 缺失数据

pandas提供了isna()notna()函数检测缺失值,它们也是Series和DataFrame对象的方法

df = pd.DataFrame(np.random.randn(5, 3), index=['a', 'c', 'e', 'f', 'h'], columns=['one', 'two', 'three'])

df.loc['c', ['two','three']] = np.nan

df.iloc[3:5, 1] = np.nan

df

    one         two         three
a   0.418430    1.052621    1.005977
c   0.046161    NaN         NaN
e   1.514924    1.489270    1.816469
f   1.327480    NaN         0.800094
h   0.736580    NaN         1.446885

检测缺失数据

df.isna()

    one     two     three
a   False   False   False
c   False   True    True
e   False   False   False
f   False   True    False
h   False   True    False

df['three'].notna()

a     True
c    False
e     True
f     True
h     True
Name: three, dtype: bool
9.1 缺失数据计算

任何数据与NaN的计算结果都是NaN

a

        one       two
a       NaN -0.282863
c       NaN  1.212112
e  0.119209 -1.044236
f -2.104569 -0.494929
h -2.104569 -0.706771

b

        one       two     three
a       NaN -0.282863 -1.509059
c       NaN  1.212112 -0.173215
e  0.119209 -1.044236 -0.861849
f -2.104569 -0.494929  1.071804
h       NaN -0.706771 -1.039575


a + b

        one  three       two
a       NaN    NaN -0.565727
c       NaN    NaN  2.424224
e  0.238417    NaN -2.088472
f -4.209138    NaN -0.989859
h       NaN    NaN -1.413542

Pandas中的统计和计算方法,都会自动解决缺失数据,例如:

  • 对数据求和时,NA值将被视为0

  • 如果所有数据都是NA,结果将是0

  • 累计方法如cumcum()cumprod()会忽略NA值,但是将它们保留在结果数组中。如果要包含NA值,可以使用skipna=False

df

    one         two         three
a   -2.091892   1.070561    0.148689
c   -0.515617   NaN         NaN
e   0.299406    1.117962    0.481406
f   1.864741    NaN         0.006480
h   -0.781525   NaN         -0.484062

df['two'].sum()

2.1885228762434497

df.mean(axis=1)

a   -0.290881
c   -0.515617
e    0.632925
f    0.935610
h   -0.632794
dtype: float64

df.cumsum(axis=1)

    one         two         three
a   -2.091892   -1.021331   -0.872642
c   -0.515617   NaN         NaN
e   0.299406    1.417368    1.898774
f   1.864741    NaN         1.871221
h   -0.781525   NaN         -1.265587

df.cumsum(axis=1, skipna=False)

    one         two         three
a   -2.091892   -1.021331   -0.872642
c   -0.515617   NaN         NaN
e   0.299406    1.417368    1.898774
f   1.864741    NaN         NaN
h   -0.781525   NaN         NaN
9.2 填充缺失数据

用标量值替换NA

df

    one         two         three
a   -2.091892   1.070561    0.148689
c   -0.515617   NaN         NaN
e   0.299406    1.117962    0.481406
f   1.864741    NaN         0.006480
h   -0.781525   NaN         -0.484062

df['two'].fillna(0)

a    1.070561
c    0.000000
e    1.117962
f    0.000000
h    0.000000
Name: two, dtype: float64

向前/向后填充

df['two'].fillna(method='pad')

a    1.070561
c    1.070561
e    1.117962
f    1.117962
h    1.117962
Name: two, dtype: float64

限制填充数量

df['two'].fillna(method='pad', limit=1)

a    1.070561
c    1.070561
e    1.117962
f    1.117962
h         NaN
Name: two, dtype: float64

常用填充方法

Method

Action

pad / ffill

向前填充值

bfill / backfill

向后填充值

9.3 使用PandasObject填充
df

    one         two         three
a   -2.091892   1.070561    0.148689
c   -0.515617   NaN         NaN
e   0.299406    1.117962    0.481406
f   1.864741    NaN         0.006480
h   -0.781525   NaN         -0.484062

df.fillna(df.mean())

    one two three
a   -2.091892   1.070561    0.148689
c   -0.515617   1.094261    0.038128
e   0.299406    1.117962    0.481406
f   1.864741    1.094261    0.006480
h   -0.781525   1.094261    -0.484062
9.4 删除缺失数据

使用dropna()可以删除行或列上的缺失数据

df

    one         two         three           four
a   -2.091892   1.070561    0.148689        NaN
c   -0.515617   NaN         NaN             NaN
e   0.299406    1.117962    0.481406        NaN
f   1.864741    NaN         0.006480        NaN
h   -0.781525   NaN         -0.484062       NaN

默认情况下dropna()在行或列有任意一个NaN时,整行或列都会被删除

df.dropna(axis=1)

    one
a   -2.091892
c   -0.515617
e   0.299406
f   1.864741
h   -0.781525

可以指定当一行或一列所有数据都为NaN删除

df.dropna(axis=1, how='all')

    one         two         three
a   -2.091892   1.070561    0.148689
c   -0.515617   NaN         NaN
e   0.299406    1.117962    0.481406
f   1.864741    NaN         0.006480
h   -0.781525   NaN         -0.484062
9.5 替换通用值

Series和DataFrame中的replace()方法可以执行灵活的替换

对于Series,可以替换单个值或值列表

ser = pd.Series([0., 1., 2., 3., 4.,3.])

ser.replace(3, -1)

0    0.0
1    1.0
2    2.0
3   -1.0
4    4.0
5   -1.0
dtype: float64

可以将值列表替换为其他值列表

ser = pd.Series([0., 1., 2.,np.nan])

ser.replace([0,1,2,np.nan],[4,5,6,0])

0    4.0
1    5.0
2    6.0
3    0.0
dtype: float64

还可以指定一个映射字典

df = pd.DataFrame({'a': [0, 1, 2, 3, 4], 'b': [5, 6, 7, 8, 9]})

df.replace({0:100, 3:300})

    a   b
0   100 5
1   1   6
2   2   7
3   300 8
4   4   9

可以使用插值的方式替换缺失值,而不用指定具体的值

ser

0    0.0
1    NaN
2    2.0
3    NaN
dtype: float64

ser.replace(np.nan, method='pad')

0    0.0
1    0.0
2    2.0
3    2.0
dtype: float64
9.6 正则表达式替换

使用正则表达式替换

d = {'a': list(range(4)), 'b': list('ab..'), 'c': ['a', 'b', np.nan, 'd']}

df = pd.DataFrame(d)

df

    a   b   c
0   0   a   a
1   1   b   b
2   2   .   NaN
3   3   .   d

df.replace(r'\s*\.\s*', np.nan, regex=True)

    a   b   c
0   0   a   a
1   1   b   b
2   2   NaN NaN
3   3   NaN d

替换几个不同的值,a替换成b.替换成NaN

df.replace(['a', '.'], ['b', np.nan])

df.replace(['a', '.'], ['b', np.nan])

    a   b   c
0   0   b   b
1   1   b   b
2   2   NaN NaN
3   3   NaN d

用正则表达式,替换几个不同的值

df.replace({'b': r'\s*\.\s*'}, {'b': np.nan}, regex=True)

    a   b   c
0   0   a   a
1   1   b   b
2   2   NaN NaN
3   3   NaN d

10. 数据分组

11. 可视化

11.1 基本绘图plot

Series和DataFrame通过 plt.plot()方法对视图做了包装

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline

ts = pd.Series(np.random.randn(1000),index=pd.date_range('1/1/2020', periods=1000))

ts = ts.cumsum()

ts.plot()
img2.png

img2.png

在DataFrame上,plt.plot()可以绘制出所有的列

df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index, columns=list('ABCD'))

df = df.cumsum()

df.plot()
img3.png

img3.png

11.2 条形图
df = pd.DataFrame({'a': np.random.randn(10) + 1, 'b': np.random.randn(10),
        'c': np.random.randn(10) - 1}, columns=['a', 'b', 'c'])

df['a'].plot.bar()
img4.png

img4.png

调用DataFrame的plot.bar()方法会产生多条图

df = pd.DataFrame({'a': np.random.randn(10) + 1, 'b': np.random.randn(10),
        'c': np.random.randn(10) - 1}, columns=['a', 'b', 'c'])

df.plot.bar()
img5.png

img5.png

要生成堆叠的条形图,传递stacked=True

df.plot.bar(stacked=True)
img6.png

img6.png

要获取水平条形图,使用barh方法

df.plot.barh(stacked=True)
img7.png

img7.png

11.3 直方图

使用hist()方法绘制直方图

df = pd.DataFrame({'a': np.random.randn(100) + 1, 'b': np.random.randn(100),
        'c': np.random.randn(100) - 1}, columns=['a', 'b', 'c'])

df.plot.hist(alpha=0.8)
img8.png

img8.png

同样可以使用stacked=True来堆叠直方图,箱尺寸可以使用bins关键字进行更改

df.plot.hist(stacked=True, bins=30)
img9.png

img9.png

可以传递matplotlib支持的其他关键字

df.plot.hist(orientation='horizontal', cumulative=True)
img10.png

img10.png

绘制多个子图

df = pd.DataFrame({'a': np.random.randn(100) + 1, 'b': np.random.randn(100),
        'c': np.random.randn(100) - 1,'d': np.random.randn(100) * 2}, columns=['a', 'b', 'c', 'd'])

df.hist(color='k', alpha=0.3, bins=30)
img11.png

img11.png

11.4 箱型图

可以使用Series.plot.box()DataFrame.plot.box()绘制箱线图,或者DataFrame.boxplot()可视化每列中值的分布

df = pd.DataFrame(np.random.rand(10, 5), columns=['A', 'B', 'C', 'D', 'E'])

df.plot.box()
img12.png

img12.png

可以通过字典传递boxeswhiskersmedianscaps的颜色参数

color = {'boxes': 'DarkGreen', 'whiskers': 'DarkOrange', 'medians': 'DarkBlue', 'caps': 'Gray'}

df.plot.box(color=color, sym='bo')
img13.png

img13.png

图像横置

color = {'boxes': 'DarkGreen', 'whiskers': 'DarkOrange', 'medians': 'DarkBlue', 'caps': 'Gray'}

df.plot.box(color=color, sym='ro', vert=False)
img14.png

img14.png

使用MatplotLib支持的关键字,例如positions更改坐标轴位置

color = {'boxes': 'DarkGreen', 'whiskers': 'DarkOrange', 'medians': 'DarkBlue', 'caps': 'Gray'}

df.plot.box(color=color, sym='ro', positions=[1, 2, 3, 4, 10])
img15.png

img15.png

DataFrame.boxplot()同样可以用来绘制箱型图

df.boxplot()
img16.png

img16.png

根据分组绘制箱型图

df = pd.DataFrame(np.random.randn(10, 2),columns=['Col1', 'Col2'])

df['X'] = pd.Series(['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'])

df.groupby('X').boxplot()
img17.png

img17.png

11.5 面积图

使用area()方法可以绘制面积图

df = pd.DataFrame(np.random.rand(10, 4), columns=['a', 'b', 'c', 'd'])

df.plot.area()
img18.png

img18.png

使用stacked=False参数,生成未堆叠的图

df.plot.area(stacked=False)
img19.png

img19.png

11.6 散点图

使用scatter()绘制散点图,散点图要求x轴和y轴为数字列,这可以通过关键字指定

df = pd.DataFrame(np.random.rand(50, 4), columns=['a', 'b', 'c', 'd'])

df.plot.scatter(x='b', y='d')
img20.png

img20.png

要在单个轴上绘制多个列组,可以通过ax方法重复指定plot。但是,最好也指定colorlabel关键字以区分每个组

ax = df.plot.scatter(x='a', y='b', color='DarkBlue', label='Group 1')

df.plot.scatter(x='c', y='d', color='DarkGreen', label='Group 2', ax=ax)
img21.png

img21.png

关键字c可以作为列的名称来给每个点提供颜色

df.plot.scatter(x='a', y='b', c='r', s=40)
img22.png

img22.png

关键字s用来指定气泡大小,可以指定DataFrame的列作为参数

df.plot.scatter(x='a', y='b', c='g', s=df['c']*50)
img23.png

img23.png

11.7 六边形图

如果数据过于密集无法绘制每个点,可以使用六边形图代替散点图。使用hexbin()方法可以绘制六边形图

df = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])

df['b'] = df['b'] + np.arange(1000)

df.plot.hexbin(x='a', y='b', gridsize=30)
img24.png

img24.png

关键字参数gridsize控制x方向上六边形的数量,默认为100。

11.8 饼图

使用pie()方法绘制饼图,如果数据包含NaN,会自动填充为0

series = pd.Series(3 * np.random.rand(4), index=['a', 'b', 'c', 'd'], name='series')

series.plot.pie()
img25.png

img25.png

对于饼图,最好使用正方形图形,即图形长宽比为1。可以创建宽度和高度相等的图形,或者在绘制后通过调用ax.set_aspect('equal')强制长宽比相等。

在DataFrame中pie()需要通过y指定列

df = pd.DataFrame(3 * np.random.rand(4, 2), index=['a', 'b', 'c', 'd'], columns=['x', 'y'])

df.plot.pie(y=0)
img26.png

img26.png

如果不指定具体列,subplots=True可以为每列绘制一个子图

df.plot.pie(subplots=True)
img27.png

img27.png

如果不想让每个子图都显示图例,用legend=False隐藏它

df.plot.pie(subplots=True, legend=False)
img28.png

img28.png

MatplotLib的其他参数同样适用

series.plot.pie(labels=['AA', 'BB', 'CC', 'DD'], colors=['r', 'g', 'b', 'c'],autopct='%.2f', fontsize=20, figsize=(6, 6))
img29.png

img29.png

如果数据总和小于1,图形会变成扇形

series = pd.Series([0.1, 0.3, 0.2, 0.1], index=['a', 'b', 'c', 'd'])

series.plot.pie()
img30.png

img30.png

11.9 缺失数据处理

不同绘图方法对缺失数据的处理方式:

图形

NaN处理方式

线

在NaN处留下空白

线(堆叠)

填0

条形图

填0

散点图

丢弃NaN

直方图

删除NaN(按列)

箱型图

删除NaN(按列)

面积图

填0

KDE

删除NaN(按列)

六边形图

丢弃NaN

饼图

填0

11.10 散点矩阵

使用 pandas.plotting中的scatter_matrix方法创建散点图矩阵

from pandas.plotting import scatter_matrix

df = pd.DataFrame(np.random.randn(100, 3), columns=['a', 'b', 'c'])

scatter_matrix(df, alpha=0.5)
img31.png

img31.png

11.11 密度图

使用kde()方法绘制密度图

pd.Series(np.random.randn(1000)).plot.kde()
img32.png

img32.png

11.12 安德鲁斯曲线

安德鲁斯曲线允许将多元数据绘制为大量曲线,这些曲线是使用样本的属性作为傅里叶级数的系数而创建的

from pandas.plotting import andrews_curves

df=pd.DataFrame(np.random.rand(5,10))

df['C'] = pd.Series(['I'+str(x) for x in range(1,6)])

andrews_curves(df, 'C')
img33.png

img33.png

11.13 平行坐标
from pandas.plotting import parallel_coordinates

df=pd.DataFrame(np.random.rand(10,10), columns=range(1,11))

df['C'] = pd.Series(['I'+str(x) for x in range(1,6)])

parallel_coordinates(df, 'C')
img34.png

img34.png

11.14 Lag Plot相关性分析

检查数据集或时间序列是否随机。随机数据在滞后图中不应显示任何结构。非随机结构意味着基础数据不是随机的

from pandas.plotting import lag_plot

spacing = np.linspace(-99 * np.pi, 99 * np.pi, num=1000)

data = pd.Series(0.1 * np.random.rand(1000) + 0.9 * np.sin(spacing))

lag_plot(data)
img35.png

img35.png

11.15 自相关图

自相关图通常用于检查时间序列中的随机性

from pandas.plotting import autocorrelation_plot

spacing = np.linspace(-9 * np.pi, 9 * np.pi, num=1000)

data = pd.Series(0.7 * np.random.rand(1000) + 0.3 * np.sin(spacing))

autocorrelation_plot(data)
img36.png

img36.png

11.16 Bootstrap 图

Bootstrap图从数据集中根据指定的次数,重复选择指定大小的随机子集,为该子集计算出相关统计信息。从而可以直观地评估统计数据的不确定性,例如均值,中位数,中间范围等。

from pandas.plotting import bootstrap_plot

data = pd.Series(np.random.rand(1000))

bootstrap_plot(data, size=50, samples=500, color='grey')
img37.png

img37.png

11.17 径向坐标

径向坐标可视化是基于弹簧张力最小化算法。它把数据集的特征映射成二维目标空间单位圆中的一个点,点的位置由系在点上的特征决定。把实例投入圆的中心,特征会朝圆中此实例位置(实例对应的归一化数值)“拉”实例。

from pandas.plotting import radviz

df = pd.DataFrame({
            'SepalLength': [6.5, 7.7, 5.1, 5.8, 7.6, 5.0, 5.4, 4.6,
                            6.7, 4.6],
            'SepalWidth': [3.0, 3.8, 3.8, 2.7, 3.0, 2.3, 3.0, 3.2,
                            3.3, 3.6],
            'PetalLength': [5.5, 6.7, 1.9, 5.1, 6.6, 3.3, 4.5, 1.4,
                            5.7, 1.0],
            'PetalWidth': [1.8, 2.2, 0.4, 1.9, 2.1, 1.0, 1.5, 0.2,
                            2.1, 0.2],
            'Category': ['virginica', 'virginica', 'setosa',
                        'virginica', 'virginica', 'versicolor',
                        'versicolor', 'setosa', 'virginica',
                        'setosa']
    })

radviz(df, 'Category')
img38.png

img38.png

11.18 绘图样式
1. 图例
df = pd.DataFrame(np.random.randn(1000, 4),index=ts.index, columns=list('ABCD'))

df = df.cumsum()

df.plot(legend=False)
img39.png

img39.png

2. 标签
df = pd.DataFrame(np.random.randn(1000, 4),index=ts.index, columns=list('ABCD'))
df = df.cumsum()
ax = df.plot(title = "title: describe figure")
ax.set_xlabel("x label")
ax.set_ylabel("y label")
plt.show()
img40.png

img40.png

3. 辅助Y轴
df = pd.DataFrame(np.random.randn(1000, 4),index=ts.index, columns=list('ABCD'))
df = df.cumsum()
df['A'].plot(legend=True)
df['B'].plot(secondary_y=True,legend=True)
img41.png

img41.png

设置辅助轴标签

df = pd.DataFrame(np.random.randn(1000, 4),index=ts.index, columns=list('ABCD'))
df = df.cumsum()
ax=df['A'].plot(legend=True)
df['B'].plot(secondary_y=True,legend=True)

ax.set_ylabel("A")
ax.right_ax.set_ylabel("B")
img42.png

img42.png

使用 mark_right=False可以取消图例中的right标志

ax=df['A'].plot(legend=True)
df['B'].plot(secondary_y=True,legend=True,mark_right=False)

ax.set_ylabel("A")
ax.right_ax.set_ylabel("B")
img43.png

img43.png

4. 指数刻度
ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2000', periods=1000))
ts = np.exp(ts.cumsum())
ts.plot(logy=True)
img44.png

img44.png

5. 取消时间轴刻度调整
ts.plot(logy=True, x_compat=True)
img45.png

img45.png

多个视图

with pd.plotting.plot_params.use('x_compat', True):
                df['A'].plot(color='r')
                df['B'].plot(color='g')
                df['C'].plot(color='b')
img46.png

img46.png

6. 子图
df.plot(subplots=True, figsize=(6, 6))
img47.png

img47.png

7. 布局定位多个图
df.plot(subplots=True, layout=(2, 3), figsize=(6, 6), sharex=False)
img48.png

img48.png

也可以通过ax关键字以列表形式传递预先创建的多个轴

fig, axes = plt.subplots(4, 4, figsize=(9, 9))
plt.subplots_adjust(wspace=0.5, hspace=0.5)
target1 = [axes[0][0], axes[1][1], axes[2][2], axes[3][3]]
target2 = [axes[3][0], axes[2][1], axes[1][2], axes[0][3]]
df.plot(subplots=True, ax=target1, legend=False, sharex=False, sharey=False)
img49.png

img49.png

8. 误差线
ix = pd.MultiIndex.from_arrays([['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b'],
                                 ['foo', 'foo', 'bar', 'bar', 'foo', 'foo', 'bar', 'bar']],
                                names=['letter', 'word'])
df = pd.DataFrame({'data1': [3, 2, 4, 3, 2, 4, 3, 2],
                    'data2': [6, 5, 7, 5, 4, 5, 6, 5]}, index=ix)
gp = df.groupby(level=('letter', 'word'))
means = gp.mean()
errors = gp.std()

means

        d       ata1    data2
letter  word
a       bar     3.5     6.0
        foo     2.5     5.5
b       bar     2.5     5.5
        foo     3.0     4.5

errors

                data1       data2
letter  word
a       bar     0.707107    1.414214
        foo     0.707107    0.707107
b       bar     0.707107    0.707107
        foo     1.414214    0.707107

means.plot.bar(yerr=errors, ax=ax, capsize=4, rot=0)    # capsize 控制误差线两端宽度
img50.png

img50.png

9. 绘制表格

绘制表格的简单方法是指定table=True

df = pd.DataFrame(np.random.randn(4, 4), columns=list('ABCD'))
fig, ax = plt.subplots(1, 1, figsize=(7, 6.5))
ax.xaxis.tick_top() # x轴刻度置于顶部
df.plot(table=True, ax=ax)
img51.png

img51.png

10. 色彩

12.数据计算

13. 时间序列

14. 设计风格

15. 选项和设置

16. 性能优化

17. 大数据集处理

18. 稀疏数据结构

第六章:开发工具

6.1 包管理工具pip

1. 安装

通常情况,pip是默认安装的,如果没有我们可以按照如下方式安装:

  • 使用easy_install安装: 各种进入到easy_install脚本的目录下,然后运行easy_inatall pip

  • 使用get-pip.py安装: 下载get-pip.py脚本 curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py 然后运行:python get-pip.py 这个脚本会同时安装setuptools和wheel工具。

  • 在linux下使用包管理工具安装pip:

    ubuntu下:sudo apt-get install python-pip

    Fedora系下:sudo yum install python-pip

  • 在windows下安装pip: 在C::raw-latex:`\python`27:raw-latex:`scirpts下运行easy`_install pip进行安装。

刚安装完毕的pip可能需要先升级一下自身: 在Linux或masOS中:

$ pip install -U pip

在windows中:

$ python -m pip install -U pip

2. 查询软件包

查询当前环境安装的所有软件包

$ pip list

查询 pypi 上含有某名字的包

$ pip search pkg

查询当前环境中可升级的包

$ pip list --outdated

查询一个包的详细内容

$ pip show pkg

3. 下载软件包

在不安装软件包的情况下下载软件包到本地

$ pip download --destination-directory /local/wheels -r requirements.txt

下载完,总归是要安装的,可以指定这个目录中安装软件包,而不从 pypi 上安装。

$ pip install --no-index --find-links=/local/wheels -r requirements.txt

当然你也从你下载的包中,自己构建生成 wheel 文件

$ pip install wheel
$ pip wheel --wheel-dir=/local/wheels -r requirements.txt

4. 安装软件包

使用 pip install <pkg> 可以很方便地从 pypi 上搜索下载并安装 python 包。

如下所示

$ pip install requests

这是安装包的基本格式,我们也可以为其添加更多参数来实现不同的效果。

4.1 只从本地安装,而不从 pypi 安装

# 前提你得保证你已经下载 pkg 包到 /local/wheels 目录下
$ pip install --no-index --find-links=/local/wheels pkg

4.2 限定版本进行软件包安装

以下三种,对单个 python 包的版本进行了约束

# 所安装的包的版本为 2.1.2
$ pip install pkg==2.1.2

# 所安装的包必须大于等于 2.1.2
$ pip install pkg>=2.1.2

# 所安装的包必须小于等于 2.1.2
$ pip install pkg<=2.1.2

以下命令用于管理/控制整个 python 环境的包版本

# 导出依赖包列表
pip freeze >requirements.txt

# 从依赖包列表中安装
pip install -r requirements.txt

# 确保当前环境软件包的版本(并不确保安装)
pip install -c constraints.txt

4.3 限制不使用二进制包安装

由于默认情况下,wheel 包的平台是运行 pip download 命令 的平台,所以可能出现平台不适配的情况。

比如在 MacOS 系统下得到的 pymongo-2.8-cp27-none-macosx_10_10_intel.whl 就不能在 linux_x86_64 安装。

使用下面这条命令下载的是 tar.gz 的包,可以直接使用 pip install 安装。

比 wheel 包,这种包在安装时会进行编译,所以花费的时间会长一些。

# 下载非二进制的包
$ pip download --no-binary=:all: pkg

# 安装非二进制的包
$ pip install pkg --no-binary

4.4 指定代理服务器安装

当你身处在一个内网环境中时,无法直接连接公网。这时候你使用pip install 安装包,就会失败。

面对这种情况,可以有两种方法:

  1. 下载离线包拷贝到内网机器中安装

  2. 使用代理服务器转发请求

第一种方法,虽说可行,但有相当多的弊端

  • 步骤繁杂,耗时耗力

  • 无法处理包的依赖问题

这里重点来介绍,第二种方法:

$ pip install --proxy [user:passwd@]http_server_ip:port pkg

每次安装包就发输入长长的参数,未免有些麻烦,为此你可以将其写入配置文件中:$HOME/.config/pip/pip.conf

对于这个路径,说明几点

  • 不同的操作系统,路径各不相同

# Linux/Unix:
/etc/pip.conf
~/.pip/pip.conf
~/.config/pip/pip.conf

# Mac OSX:
~/Library/Application Support/pip/pip.conf
~/.pip/pip.conf
/Library/Application Support/pip/pip.conf

# Windows:
%APPDATA%\pip\pip.ini
%HOME%\pip\pip.ini
C:\Documents and Settings\All Users\Application Data\PyPA\pip\pip.conf (Windows XP)
C:\ProgramData\PyPA\pip\pip.conf (Windows 7及以后)
  • 若在你的机子上没有此文件,则自行创建即可

如何配置,这边给个样例:

[global]
index-url = http://mirrors.aliyun.com/pypi/simple/

# 替换出自己的代理地址,格式为[user:passwd@]proxy.server:port
proxy=http://xxx.xxx.xxx.xxx:8080

[install]
# 信任阿里云的镜像源,否则会有警告
trusted-host=mirrors.aliyun.com

4.5 安装用户私有软件包

很多人可能还不清楚,python 的安装包是可以用户隔离的。

如果你拥有管理员权限,你可以将包安装在全局环境中。在全局环境中的这个包可被该机器上的所有拥有管理员权限的用户使用。

如果一台机器上的使用者不只一样,自私地将在全局环境中安装或者升级某个包,是不负责任且危险的做法。

面对这种情况,我们就想能否安装单独为我所用的包呢?

庆幸的是,还真有。

我能想到的有两种方法:

  1. 使用虚拟环境

  2. 将包安装在用户的环境中

虚拟环境,之前写过几篇文章,这里不再展开讲。

今天的重点是第二种方法,教你如何安装用户私有的包?

命令也很简单,只要加上 --user 参数,pip 就会将其安装在当前用户的 ~/.local/lib/python3.x/site-packages 下,而其他用户的 python 则不会受影响。

$ pip install --user pkg

来举个例子

# 在全局环境中未安装 requests
[root@localhost ~]# pip list | grep requests
[root@localhost ~]# su - wangbm
[root@localhost ~]#

# 由于用户环境继承自全局环境,这里也未安装
[wangbm@localhost ~]# pip list | grep requests
[wangbm@localhost ~]# pip install --user requests
[wangbm@localhost ~]# pip list | grep requests
requests (2.22.0)
[wangbm@localhost ~]#

# 从 Location 属性可发现 requests 只安装在当前用户环境中
[wangbm@ws_compute01 ~]$ pip show requests
---
Metadata-Version: 2.1
Name: requests
Version: 2.22.0
Summary: Python HTTP for Humans.
Home-page: http://python-requests.org
Author: Kenneth Reitz
Author-email: me@kennethreitz.org
Installer: pip
License: Apache 2.0
Location: /home/wangbm/.local/lib/python2.7/site-packages
[wangbm@localhost ~]$ exit
logout

# 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装
[root@localhost ~]$ pip list | grep requests
[root@localhost ~]$

当你身处个人用户环境中,python 导包时会先检索当前用户环境中是否已安装这个包,已安装则优先使用,未安装则使用全局环境中的包。

验证如下:

>>> import sys
>>> from pprint import pprint
>>> pprint(sys.path)
['',
 '/usr/lib64/python27.zip',
 '/usr/lib64/python2.7',
 '/usr/lib64/python2.7/plat-linux2',
 '/usr/lib64/python2.7/lib-tk',
 '/usr/lib64/python2.7/lib-old',
 '/usr/lib64/python2.7/lib-dynload',
 '/home/wangbm/.local/lib/python2.7/site-packages',
 '/usr/lib64/python2.7/site-packages',
 '/usr/lib64/python2.7/site-packages/gtk-2.0',
 '/usr/lib/python2.7/site-packages',
 '/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg',
 '/usr/lib/python2.7/site-packages/lockfile-0.12.2-py2.7.egg']
>>>

5. 卸载软件包

就一条命令,不再赘述

$ pip uninstall pkg

6. 升级软件包

想要对现有的 python 进行升级,其本质上也是先从 pypi 上下载最新版本的包,再对其进行安装。所以升级也是使用 pip install,只不过要加一个参数 --upgrade

$ pip install --upgrade pkg

在升级的时候,其实还有一个不怎么用到的选项 --upgrade-strategy,它是用来指定升级策略。

它的可选项只有两个:

  • eager :升级全部依赖包

  • only-if-need:只有当旧版本不能适配新的父依赖包时,才会升级。

在 pip 10.0 版本之后,这个选项的默认值是 only-if-need,因此如下两种写法是一互致的。

$ pip install --upgrade pkg1
$ pip install --upgrade pkg1 --upgrade-strategy only-if-need

7. 配置文件

由于在使用 pip 安装一些包时,默认会使用 pip 的官方源,所以经常会报网络超时失败。

常用的解决办法是,在安装包时,使用 -i 参数指定一个国内的镜像源。但是每次指定就很麻烦呀,还要打超长的一串字母。

这时候,其实可以将这个源写进 pip 的配置文件里。以后安装的时候,就默认从你配置的这个 源里安装了。

那怎么配置呢?文件文件在哪?

使用win+r 输入 %APPDATA% 进入用户资料文件夹,查看有没有一个 pip 的文件夹,若没有则创建之。

然后进入这个 文件夹,新建一个 pip.ini 的文件,内容如下

[global]
time-out=60
index-url=https://pypi.tuna.tsinghua.edu.cn/simple/
[install]
trusted-host=tsinghua.edu.cn

6.2 虚拟环境 - virtualenv

virtualenv 是一个创建隔绝的Python环境的工具。virtualenv创建一个包含所有必要的可执行文件的文件夹,用来使用Python工程所需的包。

1.安装

$ pip install virtualenv

2.基本使用

2.1 为一个工程创建一个虚拟环境

$ cd my_project_dir
$ virtualenv venv  # venv为虚拟环境目录名,目录名自定义

virtualenv venv 将会在当前的目录中创建一个文件夹,包含了Python可执行文件,以及 pip 库的一份拷贝,这样就能安装其他包了。虚拟环境的名字(此例中是 venv )可以是任意的;若省略名字将会把文件均放在当前目录。

在任何你运行命令的目录中,这会创建Python的拷贝,并将之放在叫做 venv 的文件中。

你可以选择使用一个Python解释器:

$ virtualenv -p /usr/bin/python2.7 venv    # -p参数指定Python解释器程序路径

这将会使用 /usr/bin/python2.7 中的Python解释器。

2.2 要开始使用虚拟环境,其需要被激活

$ source venv/bin/activate

现在起,任何你使用pip安装的包将会放在 venv 文件夹中,与全局安装的Python隔绝开。

可以像平常一样安装包,比如:

$ pip install requests

2.3 退出虚拟环境,可以使用

$ . venv/bin/deactivate

这将会回到系统默认的Python解释器,包括已安装的库也会回到默认的。

2.4 要删除一个虚拟环境,只需删除它的文件夹。(执行 ``rm -rf venv`` )

这里virtualenv 有些不便,因为virtual的启动、停止脚本都在特定文件夹,可能一段时间后,你可能会有很多个虚拟环境散落在系统各处,你可能忘记它们的名字或者位置。

3. virtualenvwrapper

鉴于virtualenv不便于对虚拟环境集中管理,所以推荐直接使用virtualenvwrapper。 virtualenvwrapper提供了一系列命令使得和虚拟环境工作变得便利。它把你所有的虚拟环境都放在一个地方。

3.1 安装virtualenvwrapper(确保virtualenv已安装)

$ pip install virtualenvwrapper

$ pip install virtualenvwrapper-win  #Windows使用该命令

安装完成后,在~/.bashrc写入以下内容

export WORKON_HOME=~/Envs
source /usr/local/bin/virtualenvwrapper.sh
# 查找virtualenvwrapper.sh可以使用:find / -name virtualenvwrapper.sh

  第一行:virtualenvwrapper存放虚拟环境目录

  第二行:virtrualenvwrapper会安装到python的bin目录下,所以该路径是python安装目录下bin/virtualenvwrapper.sh

$ source ~/.bashrc    # 读入配置文件,立即生效

3.2 virtualenvwrapper基本使用

  • 创建虚拟环境 mkvirtualenv

$ mkvirtualenv venv

这样会在WORKON_HOME变量指定的目录下新建名为venv的虚拟环境。

若想指定python版本,可通过“–python”指定python解释器

$ mkvirtualenv --python=/usr/local/python3.5.3/bin/python venv

$ mkvirtualenv -p python3 虚拟环境名称
  • 基本命令

查看当前的虚拟环境目录

$ [root@localhost ~]# workon

$ py2

$ py3
  • 切换到虚拟环境

$ [root@localhost ~]# workon py3

$ (py3) [root@localhost ~]#
  • 退出虚拟环境

$ (py3) [root@localhost ~]# deactivate

$ [root@localhost ~]#
  • 删除虚拟环境

$ rmvirtualenv venv

6.3 虚拟环境:pipenv

1.pipenv解决的问题

1.1 requirements.txt依赖管理的局限

如果我要使用 flask, 我会在requirements.txt里面写上

flask

不过由于没有指定版本,因此在另一个环境通过pip install -r requirements.txt安装依赖模块时,会默认安装最新版本的flask,如果新版本向后兼容,这当然是没问题的。但是如果新版本不兼容旧的接口,那么就出问题了:代码无法在该环境运行。因此测试环境和生产环境的不一致出现了,同一份requirement.txt,结果出来2份不同的环境,这叫做不确定构建 (the build isn’t deterministic) 问题。

这时候,可以考虑加上版本号,requirements.txt这么写

flask==0.12.1

这么写肯定可以了吧?因为以后在新环境安装pip install -r requirements.txt的时候,用的是该指定版本,就不会发生跑不起来的情况。

是这样吗?不一定哟,我们再分析一下:因为flask本身还依赖于其他模块,因此执行pip install -r requirements.txt的时候,flask使用的是0.12.1版本,但是它的依赖模块可不一定相同。比如说flask依赖Werkzeug模块,而Werkzeug模块的新版本有bug!

这时候,传统的解决方式是使用pip freeze, 它能给出当前环境下第三方模块和所有依赖子模块的版本号,因此在生产环境就可以确保使用与开发环境相同的模块了。这样我就可以把pip freeze的输出写入到requirement.txt:

click==6.7

Flask==0.12.1

itsdangerous==0.24

Jinja2==2.10

MarkupSafe==1.0

Werkzeug==0.14.1

这或许解决了生产与开发环境不一致的问题,可是它又引入了一大坨新问题!

因为,也许Werkzeug==0.14.1隐藏了一个漏洞,官方发布了0.14.2版本修复了该问题,那么我不仅需要及时下载更新模块,还得去更新requirememts.txt文件。但是那么多模块,我难道要把所有更新版本都记录下来?事实上,我本不需要记录那么多我不关心的模块的版本,我只想记录自己关心的核心模块版本。

这就出现了一个矛盾:与之间的矛盾。

1.3 多个项目依赖不同版本的子模块

假如我有2个项目A和B,A依赖django==1.9,B依赖django==1.10。linux默认使用全局共享的依赖包,因此当我在A和B项目间切换时,需要检测–卸载–安装django。

传统的解决方式是使用virtual environment ,将项目A和B的第三包隔离开。python2目前有virtualenv方案,python3目前有venv方案。

而pipenv也集成了虚拟环境管理的功能。

1.3 依赖分析

先解释一下什么叫依赖分析,假如我有一个requirments.txt如下:

package_a

package_b

如果package_a依赖于package_c>=1.0,package_b依赖于同样的package_c<=2.0, 那么在安装a,b模块时,就只能在(1.0,2.0)的中选择package_c的版本,如果安装工具有这种能力,就说它能做依赖分析。

不过pip工具没有这种依赖分析的功能。这时候只能通过在requirement.txt里面添加package_c的版本范围才能解决问题,好傻:

package_c>=1.0,<=2.0

package_a

package_b

2. 安装pipenv

$ pip install pipenv

在指定目录下创建虚拟环境, 会使用本地默认版本的python

$ pipenv install

如果要指定版本创建环境,可以使用如下命令,当然前提是本地启动目录能找到该版本的python

$ pipenv --python 3.6

激活虚拟环境

$ pipenv shell

安装第三方模块, 运行后会生成Pipfile和Pipfile.lock文件

$ pipenv install flask==0.12.1

当然也可以不指定版本:

$ pipenv install numpy

如果想只安装在开发环境才使用的包,这么做:

$ pipenv install pytest --dev

无论是生产环境还是开发环境的包都会写入一个Pipfile里面,而如果是用传统方法,需要2个文件:dev-requirements.txt 和 test-requirements.txt。

接下来如果在开发环境已经完成开发,如何构建生产环境的东东呢?这时候就要使用Pipfile.lock了,运行以下命令,把当前环境的模块lock住, 它会更新Pipfile.lock文件,该文件是用于生产环境的,你永远不应该编辑它。

$ pipenv lock

然后只需要把代码和Pipfile.lock放到生产环境,运行下面的代码,就可以创建和开发环境一样的环境咯,Pipfile.lock里记录了所有包和子依赖包的确切版本,因此是确定构建:

$ pipenv install --ignore-pipfile

如果要在另一个开发环境做开发,则将代码和Pipfile复制过去,运行以下命令:

$ pipenv install --dev

由于Pipfile里面没有所有子依赖包或者确定的版本,因此该安装可能会更新未指定模块的版本号,这不仅不是问题,还解决了一些其他问题,我在这里做一下解释:

假如该命令更新了一些依赖包的版本,由于我肯定还会在新环境做单元测试或者功能测试,因此我可以确保这些包的版本更新是不会影响软件功能的;然后我会pipenv lock并把它发布到生产环境,因此我可以确定生产环境也是不会有问题的。这样一来,我既可以保证生产环境和开发环境的一致性,又可以不用管理众多依赖包的版本,完美的解决方案!

3. 操作虚拟环境

# 返回项目的路径
$ pipenv --where

# 返回虚拟环境路径
$ pipenv --venv

# 返回该虚拟环境的解释器
$ pipenv --py

# 进入这个虚拟环境
$ pipenv shell

# 退出这个虚拟环境
$ exit
$ deactivate

# 移除当前目录的虚拟环境
$ pipenv --rm

# 在当前虚拟环境中运行
$ pipenv run python  # 进入交互式,跟直接执行 python 一样
$ pipenv run python 文件名 # 运行文件
$ pipenv run pip ...  # 运行pip

4. 虚拟环境包管理

# 安装一个本地包(setup.py)到虚拟环境(Pipfile)
$ pipenv install -e .

# 安装、卸载模块
$ pipenv install requests
$ pipenv uninstall requests
$ pipenv uninstall --all   # 卸载全部包
$ pipenv install -r path/to/requirements.txt


# 安装所有依赖
$ pipenv install --dev

# 更新包
$ pipenv update # 更新所有包
$ pipenv update --outdated # 打印所有要更新的包
$ pipenv update <包名> # 更新指定的包

# 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件
$ pipenv run pip freeze  # 相当于pipenv run pip freeze >requirements.txt

$ pipenv lock -r > requirements.txt
$ pipenv lock -r --dev # 若只想导出开发用的包

# 创建一个包含预发布的锁文件:
$ pipenv lock --pre

# 打印所有包的依赖关系图
$ pipenv graph

# 检查安全漏洞
$ pipenv check

###5. 旧项目的requirments.txt转化为Pipfile

使用pipenv install会自动检测当前目录下的requirments.txt, 并生成Pipfile, 我也可以再对生成的Pipfile做修改。

此外以下命令也有同样效果, 可以指定具体文件名:

$ pipenv install -r requirements.txt

如果我有一个开发环境的requirent-dev.txt, 可以用以下命令加入到Pipfile:

$ pipenv install -r dev-requirements.txt --dev

6.4 Jupyter Notebook

Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算:开发、文档编写、运行代码和展示结果。——Jupyter Notebook官方介绍

1. Jupyter Notebook特点

  • 编程时具有语法高亮缩进tab补全的功能。

  • 可直接通过浏览器运行代码,同时在代码块下方展示运行结果。

  • 以富媒体格式展示计算结果。富媒体格式包括:HTML,LaTeX,PNG,SVG等。

  • 对代码编写说明文档或语句时,支持Markdown语法。

  • 支持使用LaTeX编写数学性说明。

2. 安装 Jupyter Notebook

推荐使用 Anaconda,内置了Jupyter NoteBook工具

2.1 什么是Anaconda?
1. 简介

Anaconda(官方网站)就是可以便捷获取包且对包能够进行管理,同时对环境可以统一管理的发行版本。Anaconda包含了conda、Python在内的超过180个科学包及其依赖项。

2. 特点

Anaconda具有如下特点:

  • 开源

  • 安装过程简单

  • 高性能使用Python和R语言

  • 免费的社区支持

其特点的实现主要基于Anaconda拥有的:

  • conda包

  • 环境管理器

  • 1,000+开源库

如果日常工作或学习并不必要使用1,000多个库,那么可以考虑安装Miniconda(图形界面下载及命令行安装请戳),这里不过多介绍Miniconda的安装及使用。

3. Anaconda、conda、pip、virtualenv的区别

① Anaconda

  • Anaconda是一个包含180+的科学包及其依赖项的发行版本。其包含的科学包包括:conda, numpy, scipy, ipython notebook等。

② conda

  • conda是包及其依赖项和环境的管理工具。

  • 适用语言:Python, R, Ruby, Lua, Scala, Java, JavaScript, C/C++, FORTRAN。

  • 适用平台:Windows, macOS, Linux

  • 用途:

    1. 快速安装、运行和升级包及其依赖项。

    2. 在计算机中便捷地创建、保存、加载和切换环境。

    如果你需要的包要求不同版本的Python,你无需切换到不同的环境,因为conda同样是一个环境管理器。仅需要几条命令,你可以创建一个完全独立的环境来运行不同的Python版本,同时继续在你常规的环境中使用你常用的Python版本。——conda官方网站

  • conda为Python项目而创造,但可适用于上述的多种语言。

  • conda包和环境管理器包含于Anaconda的所有版本当中。

③ pip

  • pip是用于安装和管理软件包的包管理器。

  • pip编写语言:Python。

  • Python中默认安装的版本:

    • Python 2.7.9及后续版本:默认安装,命令为pip

    • Python 3.4及后续版本:默认安装,命令为pip3

  • pip名称的由来:pip采用的是递归缩写进行命名的。其名字被普遍认为来源于2处:

    • “Pip installs Packages”(“pip安装包”)

    • “Pip installs Python”(“pip安装Python”)

④ virtualenv

  • virtualenv:用于创建一个独立的Python环境的工具。

  • 解决问题:

    1. 当一个程序需要使用Python 2.7版本,而另一个程序需要使用Python 3.6版本,如何同时使用这两个程序?

    2. 如果将所有程序都安装在系统下的默认路径,如:/usr/lib/python2.7/site-packages,当不小心升级了本不该升级的程序时,将会对其他的程序造成影响。

    3. 如果想要安装程序并在程序运行时对其库或库的版本进行修改,都会导致程序的中断。

    4. 在共享主机时,无法在全局site-packages目录中安装包。

  • virtualenv将会为它自己的安装目录创建一个环境,这并不与其他virtualenv环境共享库;同时也可以选择性地不连接已安装的全局库。

⑤ pip 与 conda 比较

依赖项检查

  • pip:

    • 不一定会展示所需其他依赖包。

    • 安装包时或许会直接忽略依赖项而安装,仅在结果中提示错误。

  • conda:

    • 列出所需其他依赖包。

    • 安装包时自动安装其依赖项。

    • 可以便捷地在包的不同版本中自由切换。

环境管理

  • pip:维护多个环境难度较大。

  • conda:比较方便地在不同环境之间进行切换,环境管理较为简单。

对系统自带Python的影响

  • pip:在系统自带Python中包的**更新/回退版本/卸载将影响其他程序。

  • conda:不会影响系统自带Python。

适用语言

  • pip:仅适用于Python。

  • conda:适用于Python, R, Ruby, Lua, Scala, Java, JavaScript, C/C++, FORTRAN。

⑥ conda与pip、virtualenv的关系

  • conda结合了pip和virtualenv的功能。

2.2 Anaconda的适用平台及安装条件
1. 适用平台

Anaconda可以在以下系统平台中安装和使用:

  • Windows

  • macOS

  • Linux(x86 / Power8)

2. 安装条件
  • 系统要求:32位或64位系统均可

  • 下载文件大小:约500MB

  • 所需空间大小:3GB空间大小(Miniconda仅需400MB空间即可)

2.3 Anaconda的安装步骤
1. macOS系统安装Anaconda

① 图形界面安装

前往官方下载页面下载。

② 命令行安装

  1. 前往官方下载页面下载。有两个版本可供选择:Python 3.6 和 Python 2.7,我下载的是前者。选择版之后点击“64-Bit Command-Line Installer”进行下载。

  2. 完成下载之后,在mac的Launchpad中找到“其他”并打开“终端”。

    • 安装Python 3.6:bash ~/Downloads/Anaconda3-5.0.1-MacOSX-x86_64.sh

    • 安装Python 2.7:bash ~/Downloads/Anaconda2-5.0.1-MacOSX-x86_64.sh

  • 注意

    1. 首词bash也需要输入,无论是否用的Bash shell。

    2. 如果你的下载路径是自定义的,那么把该步骤路径中的~/Downloads替换成你自己的下载路径。

    3. 如果你将第1步下载的.sh文件重命名了,那么把该步骤路径中的Anaconda3-5.0.1-MacOSX-x86_64.shAnaconda2-5.0.1-MacOSX-x86_64.sh替换成你重命名后的文件名。

      • 强烈建议:不要修改文件名。如果重命名,使用英文进行命名。

  1. 安装过程中,看到提示“In order to continue the installation process, please review the license agreement.”(“请浏览许可证协议以便继续安装。”),点击“Enter”查看“许可证协议”。

  2. 在“许可证协议”界面将屏幕滚动至底,输入“yes”表示同意许可证协议内容。然后进行下一步。

  3. 安装过程中,提示“Press Enter to confirm the location, Press CTRL-C to cancel the installation or specify an alternate installation directory.”(“按回车键确认安装路径,按’CTRL-C’取消安装或者指定安装目录。”)如果接受默认安装路径,则会显示“PREFIX=/home//anaconda<2 or 3>”并且继续安装。安装过程大约需要几分钟的时间。

  • 建议:直接接受默认安装路径。

  1. 安装器若提示“Do you wish the installer to prepend the Anaconda install location to PATH in your /home//.bash_profile ?”(“你希望安装器添加Anaconda安装路径在/home/<user>/.bash_profile文件中吗?”),建议输入“yes”。

  • 注意

    1. 路径/home/<user>/.bash_profile中“”即进入到家目录后你的目录名。

    2. 如果输入“no”,则需要手动添加路径。添加export PATH="/<path to anaconda>/bin:$PATH"在“.bashrc”或者“.bash_profile”中。其中,“”替换为你真实的Anaconda安装路径。

  1. 当看到“Thank you for installing Anaconda!”则说明已经成功完成安装。

  2. 关闭终端,然后再打开终端以使安装后的Anaconda启动。

  3. 验证安装结果。可选用以下任意一种方法:

    1. 在终端中输入命令condal list,如果Anaconda被成功安装,则会显示已经安装的包名和版本号。

    2. 在终端中输入python。这条命令将会启动Python交互界面,如果Anaconda被成功安装并且可以运行,则将会在Python版本号的右边显示“Anaconda custom (64-bit)”。退出Python交互界面则输入exit()quit()即可。

    3. 在终端中输入anaconda-navigator。如果Anaconda被成功安装,则Anaconda Navigator的图形界面将会被启动。

2. Windows系统安装Anaconda
  1. 前往官方下载页面下载。

  2. 完成下载之后,双击下载文件,启动安装程序。

3. Linux系统安装Anaconda
  1. 前往官方下载页面下载。

  2. 启动终端,在终端中输入命令md5sum /path/filenamesha256sum /path/filename

  • 注意:将该步骤命令中的/path/filename替换为文件的实际下载路径和文件名。其中,path是路径,filename为文件名。

  • 强烈建议

    1. 路径和文件名中不要出现空格或其他特殊字符。

    2. 路径和文件名最好以英文命名,不要以中文或其他特殊字符命名。

  1. 根据Python版本的不同有选择性地在终端输入命令:

    • Python 3.6:bash ~/Downloads/Anaconda3-5.0.1-Linux-x86_64.sh

    • Python 2.7:bash ~/Downloads/Anaconda2-5.0.1-Linux-x86_64.sh

  • 注意

    1. 首词bash也需要输入,无论是否用的Bash shell。

    2. 如果你的下载路径是自定义的,那么把该步骤路径中的~/Downloads替换成你自己的下载路径。

    3. 除非被要求使用root权限,否则均选择“Install Anaconda as a user”。

  1. 安装过程中,看到提示“In order to continue the installation process, please review the license agreement.”(“请浏览许可证协议以便继续安装。”),点击“Enter”查看“许可证协议”。

  2. 在“许可证协议”界面将屏幕滚动至底,输入“yes”表示同意许可证协议内容。然后进行下一步。

  3. 安装过程中,提示“Press Enter to accept the default install location, CTRL-C to cancel the installation or specify an alternate installation directory.”(“按回车键确认安装路径,按’CTRL-C’取消安装或者指定安装目录。”)如果接受默认安装路径,则会显示“PREFIX=/home//anaconda<2 or 3>”并且继续安装。安装过程大约需要几分钟的时间。

  • 建议:直接接受默认安装路径。

  1. 安装器若提示“Do you wish the installer to prepend the Anaconda<2 or 3> install location to PATH in your /home//.bashrc ?”(“你希望安装器添加Anaconda安装路径在/home/<user>/.bashrc文件中吗?”),建议输入“yes”。

  • 注意

    1. 路径/home/<user>/.bash_rc中“”即进入到家目录后你的目录名。

    2. 如果输入“no”,则需要手动添加路径,否则conda将无法正常运行。

  1. 当看到“Thank you for installing Anaconda<2 or 3>!”则说明已经成功完成安装。

  2. 关闭终端,然后再打开终端以使安装后的Anaconda启动。或者直接在终端中输入source ~/.bashrc也可完成启动。

  3. 验证安装结果。可选用以下任意一种方法:

    1. 在终端中输入命令condal list,如果Anaconda被成功安装,则会显示已经安装的包名和版本号。

    2. 在终端中输入python。这条命令将会启动Python交互界面,如果Anaconda被成功安装并且可以运行,则将会在Python版本号的右边显示“Anaconda custom (64-bit)”。退出Python交互界面则输入exit()quit()即可。

    3. 在终端中输入anaconda-navigator。如果Anaconda被成功安装,则Anaconda Navigator将会被启动。

2.4 管理conda

接下来均是以命令行模式进行介绍,Windows用户请打开“Anaconda Prompt”;macOS和Linux用户请打开“Terminal”(“终端”)进行操作。

1. 验证conda已被安装
$ conda --version

终端上将会以conda 版本号的形式显示当前安装conda的版本号。如:conda 3.11.0

  • 注意:如果出现错误信息,则需核实是否出现以下情况:

    1. 使用的用户是否是安装Anaconda时的账户。

    2. 是否在安装Anaconda之后重启了终端。

2. 更新conda至最新版本
$ conda update conda

执行命令后,conda将会对版本进行比较并列出可以升级的版本。同时,也会告知用户其他相关包也会升级到相应版本。

当较新的版本可以用于升级时,终端会显示Proceed ([y]/n)?,此时输入y即可进行升级。

3. 查看conda帮助信息
$ conda --help

$ conda -h
4. 卸载conda

① Linux 或 macOS

$  rm -rf ~/anaconda2

$  rm -rf ~/anaconda3

即删除Anaconda的安装目录。根据安装的Anaconda版本选择相应的卸载命令。

② Windows

控制面板 → 添加或删除程序 → 选择“Python X.X (Anaconda)” → 点击“删除程序”
  • 注意

    1. Python X.X:即Python的版本,如:Python 3.6。

    2. Windows 10的删除有所不同。

2.5 管理环境

接下来均是以命令行模式进行介绍,Windows用户请打开“Anaconda Prompt”;macOS和Linux用户请打开“Terminal”(“终端”)进行操作。

1. 创建新环境
$  conda create --name <env_name> <package_names>
  • 注意

    • <env_name>即创建的环境名。建议以英文命名,且不加空格,名称两边不加尖括号“<>”。

    • <package_names>即安装在环境中的包名。名称两边不加尖括号“<>”。

      1. 如果要安装指定的版本号,则只需要在包名后面以=和版本号的形式执行。如:conda create --name python2 python=2.7,即创建一个名为“python2”的环境,环境中安装版本为2.7的python。

      2. 如果要在新创建的环境中创建多个包,则直接在<package_names>后以空格隔开,添加多个包名即可。如:conda create -n python3 python=3.5 numpy pandas,即创建一个名为“python3”的环境,环境中安装版本为3.5的python,同时也安装了numpy和pandas。

    • --name同样可以替换为-n

  • 提示:默认情况下,新创建的环境将会被保存在/Users/<user_name>/anaconda3/env目录下,其中,<user_name>为当前用户的用户名。

2. 切换环境

① Linux 或 macOS

$  source activate <env_name>

② Windows

$ activate <env_name>

③ 提示

  1. 如果创建环境后安装Python时没有指定Python的版本,那么将会安装与Anaconda版本相同的Python版本,即如果安装Anaconda第2版,则会自动安装Python 2.x;如果安装Anaconda第3版,则会自动安装Python 3.x。

  2. 当成功切换环境之后,在该行行首将以“(env_name)”或“[env_name]”开头。其中,“env_name”为切换到的环境名。如:在macOS系统中执行source active python2,即切换至名为“python2”的环境,则行首将会以(python2)开头。

3. 退出环境至root

① Linux 或 macOS

$ source deactivate

② Windows

$ deactivate

③ 提示

当执行退出当前环境,回到root环境命令后,原本行首以“(env_name)”或“[env_name]”开头的字符将不再显示。

4. 显示已创建环境
$ conda info --envs

$ conda info -e

$ conda env list
5. 复制环境
$ conda create --name <new_env_name> --clone <copied_env_name>
  • 注意

    1. <copied_env_name>即为被复制/克隆环境名。环境名两边不加尖括号“<>”。

    2. <new_env_name>即为复制之后新环境的名称。环境名两边不加尖括号“<>”。

    3. 如:conda create --name py2 --clone python2,即为克隆名为“python2”的环境,克隆后的新环境名为“py2”。此时,环境中将同时存在“python2”和“py2”环境,且两个环境的配置相同。

6. 删除环境
$ conda remove --name <env_name> --all
  • 注意<env_name>为被删除环境的名称。环境名两边不加尖括号“<>”。

2.6 管理包
1. 查找可供安装的包版本

① 精确查找

$ conda search --full-name <package_full_name>
  • 注意

    1. --full-name为精确查找的参数。

    2. <package_full_name>是被查找包的全名。包名两边不加尖括号“<>”。

  • 例如conda search --full-name python即查找全名为“python”的包有哪些版本可供安装。

② 模糊查找

$ conda search <text>
  • 注意<text>是查找含有此字段的包名。此字段两边不加尖括号“<>”。

  • 例如conda search py即查找含有“py”字段的包,有哪些版本可供安装。

2. 获取当前环境中已安装的包信息
$ conda list

执行上述命令后将在终端显示当前环境已安装包的包名及其版本号。

3. 安装包

① 在指定环境中安装包

$ conda install --name <env_name> <package_name>
  • 注意

    1. <env_name>即将包安装的指定环境名。环境名两边不加尖括号“<>”。

    2. <package_name>即要安装的包名。包名两边不加尖括号“<>”。

  • 例如conda install --name python2 pandas即在名为“python2”的环境中安装pandas包。

② 在当前环境中安装包

$ conda install <package_name>
  • 注意

    1. <package_name>即要安装的包名。包名两边不加尖括号“<>”。

    2. 执行命令后在当前环境中安装包。

  • 例如conda install pandas即在当前环境中安装pandas包。

③ 使用pip安装包

使用场景

当使用conda install无法进行安装时,可以使用pip进行安装。例如:see包。

命令

$ pip install <package_name>
  • 注意:为指定安装包的名称。包名两边不加尖括号“<>”。

  • 例如pip install see即安装see包。

注意

  1. pip只是包管理器,无法对环境进行管理。因此如果想在指定环境中使用pip进行安装包,则需要先切换到指定环境中,再使用pip命令安装包。

  2. pip无法更新python,因为pip并不将python视为包。

  3. pip可以安装一些conda无法安装的包;conda也可以安装一些pip无法安装的包。因此当使用一种命令无法安装包时,可以尝试用另一种命令。

4. 卸载包

① 卸载指定环境中的包

$ conda remove --name <env_name> <package_name>
  • 注意

    1. <env_name>即卸载包所在指定环境的名称。环境名两边不加尖括号“<>”。

    2. <package_name>即要卸载包的名称。包名两边不加尖括号“<>”。

  • 例如conda remove --name python2 pandas即卸载名为“python2”中的pandas包。

② 卸载当前环境中的包

$ conda remove <package_name>
  • 注意

    1. <package_name>即要卸载包的名称。包名两边不加尖括号“<>”。

    2. 执行命令后即在当前环境中卸载指定包。

  • 例如conda remove pandas即在当前环境中卸载pandas包。

5. 更新包

① 更新所有包

$ conda update --all

$ conda upgrade --all
  • 建议:在安装Anaconda之后执行上述命令更新Anaconda中的所有包至最新版本,便于使用。

② 更新指定包

$ conda update <package_name>

$ conda upgrade <package_name>
  • 注意

    1. <package_name>为指定更新的包名。包名两边不加尖括号“<>”。

    2. 更新多个指定包,则包名以空格隔开,向后排列。如:conda update pandas numpy matplotlib即更新pandas、numpy、matplotlib包。

3. Jupyter Notebook配置

3.1. 生成配置文件

如果服务器上你的账户下已有默认 jupyter 用户的配置文件,可以直接拷贝一份,改个名字,比如:

$ cd /root/.jupyter

$ cp jupyter_notebook_config.py jupyter_my_config.py

或者,直接自己找个任意目录,比如 /root/my_configs,直接创建一个新文件作为配置文件:

$ mkdir /root/my_configs

$ cd /root/my_configs

$ touch jupyter_notebook_config.py

再或者,账户下未建立默认 jupyter 配置文件的情况下,可以自动生成:

$ jupyter notebook --generate-config
3.2. 编辑配置文件

打开 jupyter_notebook_config.py 文件:

$ vim jupyter_notebook_config.py

可以看到全是注释的配置说明,比较复杂,也不是都用得上,这里我们自己写一些重要的配置即可,在文件开头写入:

c = get_config()

c.IPKernelApp.pylab = "inline"

c.NotebookApp.ip = "*"

c.NotebookAPp.open_browser = False

c.NotebookApp.password = 'sha1:b39d2445079f:9b9ab99f65150e113265cb99a841a6403aa52647'

c.NotebookApp.certfile = u'/root/.jupyter/mycert.pem'

c.NotebookApp.port= 8888

c.NotebookApp.notebook_dir = "/root/ipython"

注意1:第五行 password 填入的是,通过以下方式生成:

[root@VM_157_11_centos .jupyter]# python

Python 2.7.5 (default, Aug 4 2017, 00:39:18)

[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux2

Type "help", "copyright", "credits" or "license" for more information.

>>> from IPython.lib import passwd

>>> passwd()

Enter password:

Verify password:

'sha1:175e8efe8974:eacef02a2e3f959d6efdf6c93d142c7f4712f5cc'

>>> exit()

注意2:第六行的 certfile 证书文件可以通过下面这行命令生成(中间的交互信息可以随便填),注意路径要对应上:

$ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem

注意3:第七行的 port 应该是一个未被占用的、被防火墙允许的端口(在上面的步骤我们已经打开了 8888 端口),这里再强调一遍(同样的,腾讯云等服务器需要在官网修改安全策略):

$ firewall-cmd --zone=public --add-port=8888/tcp --permanent

success # 系统反馈信息

$ systemctl restart firewalld.service

注意4:第八行的 notebook_dir 是你的文档目录,需要自行选择并创建(否则运行时会报错):

$ mkdir /root/ipython

运行

$ jupyter notebook *--config jupyter_notebook_config.py --allow-root*

关于参数:–config 是可选的,默认会用 jupyter_notebook_config.py 文件,如果有多个用户配置文件(给多个用户分别提供 jupyter notebook),就必须要用这个命令了。–allow-root 是 root 用户启动 jupyter notebook 时的必须参数,实际上不建议使用 root 启动 jupyter notebook,最好还是用其他用户启动,这样在 浏览器端 cmd 窗口就不至于直接暴露 root 权限。

后台运行: 实际使用的时候我们当然会让 jupyter notebook 在后台一直运行着,即使我断开 ssh 连接之后也要可以通过浏览器访问。那也简单,用 nohup 命令就可以了:

$ nohup jupyter notebook --config jupyter_notebook_config.py --allow-root 2>&1 > my.log &

用该命令启动 jupyter notebook 之后,原先打印在屏幕上行的日志会写入到 my.log 文本文件中(该文件路径可以替换,当然完全不想要日志的话也可以重定向到 /dev/null)。

4. 主题字体设置及自动代码补全

github上发现了一个jupyter-themes工具,可以通过pip安装,非常方便使用。

首先是主题下载,命令行如下所示:

$ pip install --no-dependencies jupyterthemes==0.18.2

安装好了,有的电脑可能会提示缺少 lesscpy,继续 pip 安装

$ pip install lesscpy

然后是对主题选择、字体大小进行设置,我总结了一个我最喜欢的

$ jt --lineh 140 -f consolamono -tf ptmono -t grade3 -ofs 14 -nfs 14 -tfs 14 -fs 14 -T -N

命令行的格式的解释如下表所示

cl options

arg

default

Usage help

-h

List Themes

-l

Theme Name to Install

-t

Code Font

-f

Code Font-Size

-fs

11

Notebook Font

-nf

Notebook Font Size

-nfs

13

Text/MD Cell Font

-tf

Text/MD Cell Fontsize

-tfs

13

Pandas DF Fontsize

dfs

9

Output Area Fontsize

-ofs

8.5

Mathjax Fontsize (%)

-mathfs

100

Intro Page Margins

-m

auto

Cell Width

-cellw

980

Line Height

-lineh

170

Cursor Width

-cursw

2

Cursor Color

-cursc

Alt Prompt Layout

-altp

Alt Markdown BG Color

-altmd

Alt Output BG Color

-altout

Style Vim NBExt*

-vim

Toolbar Visible

-T

Name & Logo Visible

-N

Reset Default Theme

-r

Force Default Fonts

-dfonts

接着让 jupyter notebook 实现自动代码补全,首先安装 nbextensions

$ pip install jupyter_contrib_nbextensions

$ jupyter contrib nbextension install --user

然后安装 nbextensions_configurator

$ pip install jupyter_nbextensions_configurator

如果提示缺少依赖,就使用pip安装对应依赖即可。

最后重启jupyter,在弹出的主页面里,能看到增加了一个Nbextensions标签页,在这个页面里,勾选Hinterland即启用了代码自动补全,如图所示:

1330952-20180911211947213-1976856179

1330952-20180911211947213-1976856179

5. JupyterLab

5.1 简介

JupyterLab是Jupyter主打的最新数据科学生产工具,某种意义上,它的出现是为了取代Jupyter Notebook。

JupyterLab有以下特点:

  • 交互模式:Python交互式模式可以直接输入代码,然后执行,并立刻得到结果,因此Python交互模式主要是为了调试Python代码用的

  • 内核支持的文档:使你可以在可以在Jupyter内核中运行的任何文本文件(Markdown,Python,R等)中启用代码

  • 模块化界面:可以在同一个窗口同时打开好几个notebook或文件(HTML, TXT, Markdown等等),都以标签的形式展示,更像是一个IDE

  • 镜像notebook输出:让你可以轻易地创建仪表板

  • 同一文档多视图:使你能够实时同步编辑文档并查看结果

  • 支持多种数据格式:你可以查看并处理多种数据格式,也能进行丰富的可视化输出或者Markdown形式输出

  • 云服务:使用Jupyter Lab连接Google Drive等服务,极大得提升生产力

5.2 安装Jupyter Lab

你可以使用pipconda安装Jupyter Lab

pip pip可能是大多数人使用包管理工具,如果使用pip安装,请在命令行执行:

pip install jupyterlab

conda 如果你是Anaconda用户,那么可以直接用conda安装,请在命令行执行:

conda install -c conda-forge jupyterlab
5.3 运行Jupyter Lab

在安装Jupyter Lab后,接下来要做的是运行它。 你可以在命令行使用jupyter-labjupyter lab命令,然后默认浏览器会自动打开Jupyter Lab。


6.7 mongodb指南

1. 简介

MongoDB 是一个基于分布式文件存储的数据库。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

通过对比SQL,可以对mongodb中的文档、集合、数据库有一个基本了解:

SQL术语/概念

MongoDB术语/概念

解释/说明

database

database

数据库

table

collection

数据库表/集合

row

document

数据记录行/文档

column

field

数据字段/域

index

index

索引

table joins

表连接,MongoDB不支持

primary key

primary key

主键,MongoDB自动将_id字段设置为主键

MongoDB的特点

  • 面向集合存储,易存储对象类型的数据。

  • 模式自由

  • 支持动态查询

  • 可通过网络访问

  • 支持查询

  • 支持复制和故障恢复

  • 支持完全索引,包含内部对象

  • 文件存储格式为BSON(一种JSON的扩展)

  • 自动处理碎片,以支持云计算层次的扩展性

  • 使用高效的二进制数据存储,包括大型对象(如视频等)

  • 支持 GolangRUBYPYTHONJAVAC++PHPC# 等多种语言

  • MongoDB安装简单。

MongoDB 的适用场景

MongoDB 的主要目标是在键/值存储方式(提供了高性能和高度伸缩性)和传统的RDBMS 系统(具有丰富的功能)之间架起一座桥梁,它集两者的优势于一身。根据官方网站的描述,Mongo 适用于以下场景。

  • 网站数据:Mongo 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。

  • 缓存:由于性能很高,Mongo 也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo 搭建的持久化缓存层可以避免下层的数据源过载。

  • 高伸缩性的场景:Mongo 非常适合由数十或数百台服务器组成的数据库,Mongo 的路线图中已经包含对MapReduce 引擎的内置支持。

  • 用于对象及JSON 数据的存储:Mongo 的BSON 数据格式非常适合文档化格式的存储及查询。

MongoDB 的使用也会有一些限制,例如,它不适合于以下几个地方。

  • 高度事务性的系统:例如,银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。

  • 传统的商业智能应用:针对特定问题的BI 数据库会产生高度优化的查询方式。对于此类应用,数据仓库可能是更合适的选择。

  • 需要SQL 的问题。

2. 创建、删除、更新

2.1 创建

文档的数据结构和JSON基本一样。

所有存储在集合中的数据都是BSON格式。

BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON。

MongoDB 使用 insert()save() 方法向集合中插入文档,语法如下:

db.COLLECTION_NAME.insert(document)

db.COLLECTION_NAME.save(document)
  • save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 db.collection.insertOne()db.collection.replaceOne() 来代替。

  • insert(): 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。

3.2 版本之后新增了 db.collection.insertOne() 和 db.collection.insertMany()。

db.collection.insertOne() 用于向集合插入一个新文档,语法格式如下:

db.collection.insertOne(
   <document>,
   {
      writeConcern: <document>
   }
)

db.collection.insertMany() 用于向集合插入一个多个文档,语法格式如下:

db.collection.insertMany(
   [ <document 1> , <document 2>, ... ],
   {
      writeConcern: <document>,
      ordered: <boolean>
   }
)

参数说明:

  • document:要写入的文档。

  • writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。

  • ordered:指定是否按顺序写入,默认 true,按顺序写入。

2.2 删除

remove()函数是用来移除集合中的数据

db.collection.remove(
   <query>,
   <justOne>
)

参数说明:

  • query :(可选)删除的文档的条件。

  • justOne : (可选)如果设为 true 或 1,则只删除一个文档。

  • writeConcern :(可选)抛出异常的级别。

2.3 更新

使用 update()save() 方法来更新集合中的文档

update() 方法

update() 方法用于更新已存在的文档。语法格式如下:

db.collection.update(
   <query>,
   <update>,
   {
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>
   }
)

参数说明:

  • query : update的查询条件,类似sql update查询内where后面的。

  • update : update的对象和一些更新的操作符(如\(,\)inc…)等,也可以理解为sql update查询内set后面的

  • upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。

  • multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。

  • writeConcern :可选,抛出异常的级别。

save() 方法

save() 方法通过传入的文档来替换已有文档,_id 主键存在就更新,不存在就插入。语法格式如下:

db.collection.save(
   <document>,
   {
     writeConcern: <document>
   }
)

参数说明:

  • document : 文档数据。

  • writeConcern :可选,抛出异常的级别。

3. 查询

3.1 find查询

MongoDB 查询数据的语法格式如下:

>db.collection.find(query, projection)
  • query :可选,使用查询操作符指定查询条件

  • projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。

如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:

>db.col.find().pretty()

pretty() 方法以格式化的方式来显示所有文档。除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。

3.2 查询条件
1. 条件查询

MongoDB中条件操作符有:

  • (>) 大于 - $gt

  • (<) 小于 - $lt

  • (>=) 大于等于 - $gte

  • (<= ) 小于等于 - $lte

MongoDB 与 RDBMS Where 语句比较

2. OR查询

MongoDB中有两种方式进行OR查询:

$in可以用来查询一个键的多个值;

$or更通用一些,可以在多个键中查询任意的给定值。

如果一个键需要和多个值进行匹配的话,就要有$in操作符

>db.descinfo.find({"views":{"$in":[143, 23, 444]}})

$in非常灵活,可以指定不用类型的条件和值

>db.descinfo.find({"user_id":{"$in":[143, "tom", 444]}})

如果$in对应的数组只有一个值,那么和直接匹配这个值的效果是一样的

>db.descinfo.find({"user_id":{"$in":[143]}})
等价于
>db.descinfo.find({"user_id":143})

$in相对的是$nin$nin返回与数组中所有条件都不匹配的文档

>db.descinfo.find({"views":{"$in":[143, 23, 444]}})

查询结果返回views值不是143、23、444的所有文档

$in能对单个键做OR查询,如果需要查询类似于“age”为20或“name”为“张三”的文档,就需要用$or$or接受一个包含所有可能条件的数组作为参数。

>db.student.find({"$or":[{"age":20},{"name":"张三"}]})

使用普通的AND型查询时,总是希望尽可能用最少的条件来限定结果的范围。OR型查询正相反:第一个条件应该尽可能匹配更多的文档,这样才是最为高效的。

$or在任何情况下都会正常工作。如果查询优化器可以更高效地处理$in​,那就选择使用它。

3. $not

$not是元条件句,即可以用在任何其他条件之上。

就拿取模运算符$mod来说。$mod会将查询的值除以第一个给定值,若余数等于第二个给定值则匹配成功:

>db.users.find({"id_num":{"$mod":[5,1]}})

上面的查询会返回“id_num”值为1、6、11、16等的用户。但要是想返回“id_num”为2、3、4、5、7、8、9、10、12等的用户,就要用$not了:

>db.users.find({"id_num":{"not":{"$mod":[5,1]}}})

4. 特定类型查询

4.1 null

null不仅会匹配某个键的值为null的文档,而且还会匹配不包含这个键的文档。所以,这种匹配还会返回缺少这个键的所有文档:

如果仅想匹配键值为null的文档,既要检查该键的值是否为null,还要通过“$exists”条件判定键值已存在:

>db.c.find({"z":{"$in":[null],"$exists":true}})

注意:MongoDB中没有$eq操作符,但是使用只有一个元素的$in操作符效果是一样的。

4.2 查询数组

查询数组元素与查询标量值是一样的。例如,有一个水果列表,如下所示:

>db.food.insert({"fruit":["apple","banana","peach"]})

下面的查询:

>db.food.find({"fruit":"banana"})

会成功匹配该文档。这个查询好比我们对一个这样的(不合法)文档进行查询:{“fruit”:“apple”,“fruit”:“banana”,“fruit”:“peach”}

$all

如果需要通过多个元素来匹配数组,就要用$all了。这样就会匹配一组元素。例如,假设创建了一个包含3个元素的集合:

>db.food.insert({"_id":1,"fruit":["apple","banana","peach"]})

>db.food.insert({"_id":2,"fruit":["apple","kumquat","orange"]})

>db.food.insert({"_id":3,"fruit":["cherry","banana","apple"]})

要找到既有“apple”又有“banana”的文档,可以使用$all

>db.food.find({fruit:{$all:["apple","banana"]}})

{"_id":1,"fruit":["apple","banana","peach"]}
{"_id":3,"fruit":["cherry","banana","apple"]}

这里的顺序无关紧要。注意,第二个结果中“banana”“apple”之前。要是对只有一个元素的数组使用$all,就和不用$all一样了。例如,{fruit:{$all:[‘apple’]}{fruit:‘apple’}的查询结果完全一样。

也可以使用整个数组进行精确匹配。但是,精确匹配对于缺少元素或者元素冗余的情况就不大灵了。例如,下面的方法会匹配之前的第一个文档:

>db.food.find({"fruit":["apple","banana","peach"]})

但是下面这个就不会匹配:

>db.food.find({"fruit":["apple","banana"]})

这个也不会匹配:

>db.food.find({"fruit":["banana","apple","peach"]})

要是想查询数组特定位置的元素,需使用key.index语法指定下标:

>db.food.find({"fruit.2":"peach"})

数组下标都是从0开始的,所以上面的表达式会用数组的第3个元素和“peach”进行匹配。

$size

$size对于查询数组来说也是非常有用的,顾名思义,可以用它查询特定长度的数组。例如:

>db.food.find({"fruit":{"$size":3}})

得到一个长度范围内的文档是一种常见的查询。$size并不能与其他查询条件(比如$gt)组合使用,但是这种查询可以通过在文档中添加一个“size”键的方式来实现。这样每一次向指定数组添加元素时,同时增加“size”的值。比如,原本这样的更新:

>db.food.update(criteria,{"$push":{"fruit":"strawberry"}})

就要变成下面这样:

>db.food.update(criteria,{"$push":{"fruit":"strawberry"},"$inc":{"size":1}})

自增操作的速度非常快,所以对性能的影响微乎其微。这样存储文档后,就可以像下面这样查询了:

>db.food.find({"size":{"$gt":3}})

$slice操作符

find的第二个参数是可选的,可以指定需要返回的键。这个特别的$slice操作符可以返回某个键匹配的数组元素的一个子集。

假设现在有一个博客文章的文档,我们希望返回前10条评论,可以这样做:

>db.blog.posts.findOne(criteria,{"comments":{"$slice":10}})

也可以返回后10条评论:

>db.blog.posts.findOne(criteria,{"comments":{"$slice":-10}})

$slice也可以指定偏移值以及希望返回的元素数量,来返回元素集合中间位置的某些结果:

>db.blog.posts.findOne(criteria,{"comments":{"$slice":[23,10]}})

这个操作会跳过前23个元素,返回第24~33个元素。如果数组不够33个元素,则返回第23个元素后面的所有元素。

除非特别声明,否则使用$slice时将返回文档中的所有键。别的键说明符都是默认不返回未提及的键,这点与$slice不太一样。例如,有如下博客文章文档:

{
    "_id": ObjectId("4b2d75476cc613d5ee930164"),
    "title": "Ablogpost",
    "content": "...",
    "comments": [{
            "name": "joe",
            "email": "joe@example.com",
            "content": "nicepost."
        },
        {
            "name": "bob",
            "email": "bob@example.com",
            "content": "goodpost."
        }
    ]
}

$slice来获取最后一条评论,可以这样:

>db.blog.posts.findOne(criteria,{"comments":{"$slice":1}})

{
    "_id": ObjectId("4b2d75476cc613d5ee930164"),
    "title": "Ablogpost",
    "content": "...",
    "comments": [{
        "name": "bob",
        "email": "bob@example.com",
        "content": "goodpost."
    }]
}

“title”“content”都返回了,即便是并没有显式地出现在键说明符中。

返回一个匹配的数组元素

如果知道元素的下标,那么$slice非常有用。但有时我们希望返回与查询条件相匹配的任意一个数组元素。可以使用$操作符得到一个匹配的元素。对于上面的博客文章示例,可以用如下的方式得到Bob的评论:

>db.blog.posts.find({"comments.name":"bob"},{"comments.$":1})

{
    "_id": ObjectId("4b2d75476cc613d5ee930164"),
    "comments": [{
        "name": "bob",
        "email": "bob@example.com",
        "content": "goodpost."
    }]
}

注意,这样只会返回第一个匹配的文档。如果Bob在这篇博客文章下写过多条评论,只有“comments”数组中的第一条评论会被返回。

4.3 正则表达式

MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。MongoDB使用PCRE (Perl Compatible Regular Expression) 作为正则表达式语言。不同于全文检索,我们使用正则表达式不需要做任何配置。

考虑以下profile集合的文档结构,该文档包含了文章内容和标签:

{
    "_id" : "xinhuashefabu1",
    "aliasName" : "xinhuashefabu1",
    "logo_url" : "http://wx.qlogo.cn/mmhead/Q3auHgzwzM6CRL0IbOnOf9n66mYHko2JPHX9GCPqPkSlHzibCHnua3w/96",
    "detail" : "新华通讯社官方账号。新华社是中国国家通讯社,现场新闻、原创新闻报道的大本营。",
    "wxId" : "gh_6651e07e4b2d",
    "name" : "新华社",
    "register" : "新华新媒文化传播有限公司",
    "biz" : "MzA4NDI3NjcyNA==",
    "links" : [
        {
            "url" : "https://xhpfmapi.zhongguowangshi.com/vh512/sceneList/9",
            "word" : "现场云"
        },
        {
            "url" : "https://xhpfmapi.zhongguowangshi.com/vh512/fasttheme/25367",
            "word" : "AI主播"
        },
        {
            "userName" : "gh_345c5d37f42f@app",
            "weappUrl" : "/pages/home-page/index.html?p=63",
            "word" : "掌上高铁"
        }
    ],
    "tags": [
      "新闻",
      "新闻资讯",
      "热门"
    ]
}

使用正则表达式

以下命令使用正则表达式查找包含 “新华社” 字符串的文章:

>db.profile.find({register:{$regex:"新华社"}})

以上查询也可以写为:

>db.profile.find({post_text:/新华社/})

不区分大小写的正则表达式

如果检索需要不区分大小写,我们可以设置 $options$i

以下命令将查找不区分大小写的字符串 xinhua:

>db.profile.find({aliasName:{$regex:"xinhua",$options:"$i"}})

数组元素使用正则表达式

我们还可以在数组字段中使用正则表达式来查找内容。 这在标签的实现上非常有用,如果需要查找包含以新闻开头的标签数据,使用以下代码:

>db.profile.find({tags:{$regex:"新闻"}})

优化正则表达式查询

  • 如果对文档中字段设置了索引,那么使用索引相比于正则表达式匹配查找所有的数据查询速度更快。

  • 如果正则表达式是前缀表达式,所有匹配的数据将以指定的前缀字符串为开始。例如: 如果正则表达式为 ^tut ,查询语句将查找以 tut 为开头的字符串。

这里面使用正则表达式有两点需要注意:

正则表达式中使用变量。一定要使用eval将组合的字符串进行转换,不能直接将字符串拼接后传入给表达式。否则没有报错信息,只是结果为空!实例如下:

var name=eval("/" + 变量值key +"/i");

以下是模糊查询包含title关键词, 且不区分大小写:

title:eval("/"+title+"/i")    // 等同于 title:{$regex:title,$Option:"$i"}
4.4 $where

键/值对是一种表达能力非常好的查询方式,但是依然有些需求它无法表达。其他方法都败下阵时,就轮到$where子句登场了,用它可以在查询中执行任意的JavaScript。这样就能在查询中做(几乎)任何事情。

注意:为安全起见,应该严格限制或者消除$where语句的使用。应该禁止终端用户使用任意的$where语句。

$where语句最常见的应用就是比较文档中的两个键的值是否相等。假如我们有如下文档:

>db.foo.insert({"apple":1,"banana":6,"peach":3})

>db.foo.insert({"apple":8,"spinach":4,"watermelon":4})

我们希望返回两个键具有相同值的文档。第二个文档中,“spinach”“watermelon”的值相同,所以需要返回该文档。

MongoDB似乎从来没有提供过一个条件语句来做这种查询,所以只能用$where子句借助JavaScript来完成了:

>db.foo.find({"$where":function(){
    for(varcurrentinthis){
        for(varotherinthis){
            if(current!=other&&this[current]==this[other]){
                return true;
            }
        }
    }
    return false;
    }});

如果函数返回true,文档就做为结果集的一部分返回;如果为false,就不返回。

不是非常必要时,一定要避免使用$where查询,因为它们在速度上要比常规查询慢很多。每个文档都要从BSON转换成JavaScript对象,然后通过$where表达式来运行。而且$where语句不能使用索引,所以只在走投无路时才考虑$where这种用法。先使用常规查询进行过滤,然后再使用$where语句,这样组合使用可以降低性能损失。如果可能的话,使用$where语句前应该先使用索引进行过滤,$where只用于对结果进行进一步过滤。

5. 游标

5.1 limit、skip和sort

limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。

>db.COLLECTION_NAME.find().limit(NUMBER)

skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。

>db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)

避免使用skip()跳过大量数据

跳过数量过多会导致skip()查询变得很慢,因为要先找到需要被略过的数据,然后再抛弃这些数据。大多数数据库都会在索引中保存更多的元数据,用于处理skip,但是MongoDB目前还不支持,所以要尽量避免略过太多的数据。通常可以利用上次的结果来计算下一次查询条件。

sort()方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。

>db.COLLECTION_NAME.find().sort({KEY:1})
5.2 游标生命周期

看待游标有两种角度:客户端的游标以及客户端游标表示的数据库游标。

在服务器端,游标消耗内存和其他资源。游标遍历尽了结果以后,或者客户端发来消息要求终止,数据库将会释放这些资源。释放的资源可以被数据库另作他用,这是非常有益的,所以要尽量保证尽快释放游标(在合理的前提下)。

还有一些情况导致游标终止(随后被清理)。首先,游标完成匹配结果的迭代时,它会清除自身。另外,如果客户端的游标已经不在作用域内了,驱动程序会向服务器发送一条特别的消息,让其销毁游标。最后,即便用户没有迭代完所有结果,并且游标也还在作用域中,如果一个游标在10分钟内没有使用的话,数据库游标也会自动销毁。这样的话,如果客户端崩溃或者出错,MongoDB就不需要维护这上千个被打开却不再使用的游标。

这种“超时销毁”的行为是我们希望的:极少有应用程序希望用户花费数分钟坐在那里等待结果。然而,有时的确希望游标持续的时间长一些。若是如此的话,多数驱动程序都实现了一个叫immortal的函数,或者类似的机制,来告知数据库不要让游标超时。如果关闭了游标的超时时间,则一定要迭代完所有结果,或者主动将其销毁,以确保游标被关闭。否则它会一直在数据库中消耗服务器资源。

6. 索引

6.1 索引简介

索引是对数据库表中一列或多列的值进行排序的一种结构。数据库可以直接在索引中查找,在索引中找到条目以后,就可以直接跳转到目标文档的位置,这能使查找速度提高几个数量级。

举个例子,我们创建一个很大的文档集合:

for(i=0;i<1000000;i++){
    db.users.insert(
    {
        "i":i,
        "username":"user"+i,
        "age":Math.floor(Math.random()*100 + 1)
        }
    );
}

查询一个随机的username,需要知道的是,可以使用explain()返回查询过程的详细描述

db.users.find({username:'user1003'}).explain('executionStats')

执行结果:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "users",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "username" : {
                "$eq" : "user1003"
            }
        },
        "winningPlan" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "username" : {
                    "$eq" : "user1003"
                }
            },
            "direction" : "forward"
        },
        "rejectedPlans" : []
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 337,
        "totalKeysExamined" : 0,
        "totalDocsExamined" : 1000000,
        "executionStages" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "username" : {
                    "$eq" : "user1003"
                }
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 290,
            "works" : 1000002,
            "advanced" : 1,
            "needTime" : 1000000,
            "needYield" : 0,
            "saveState" : 7812,
            "restoreState" : 7812,
            "isEOF" : 1,
            "invalidates" : 0,
            "direction" : "forward",
            "docsExamined" : 1000000
        }
    },
    "ok" : 1.0
}

可以看到执行整个查询共计扫描文档数1000000,耗时337毫秒。虽然返回结果只有一个,但是由于不知道集合中的username字段是否唯一,MongoDB不得不查看集合中的每一个文档。

类似于此类查询,索引可以根据给定的字段组织数据,让MongoDB更快地找到目标文档,下面我们创建一个索引:

db.users.ensureIndex({'username':1})

再次执行上面的查询,用explain()查看详细描述

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "users",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "username" : {
                "$eq" : "user1003"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "username" : 1.0
                },
                "indexName" : "username_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "username" : []
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "username" : [
                        "[\"user1003\", \"user1003\"]"
                    ]
                }
            }
        },
        "rejectedPlans" : []
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 0,
        "totalKeysExamined" : 1,
        "totalDocsExamined" : 1,
        "executionStages" : {
            "stage" : "FETCH",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 2,
            "advanced" : 1,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 1,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 1,
                "executionTimeMillisEstimate" : 0,
                "works" : 2,
                "advanced" : 1,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "username" : 1.0
                },
                "indexName" : "username_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "username" : []
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "username" : [
                        "[\"user1003\", \"user1003\"]"
                    ]
                },
                "keysExamined" : 1,
                "seeks" : 1,
                "dupsTested" : 0,
                "dupsDropped" : 0,
                "seenInvalidated" : 0
            }
        }
    },
    "ok" : 1.0
}

可以看到,查询过程瞬间完成。

但是要注意,使用索引是有代价的:对于添加的每一个索引,每次写操作(插入、更新、删除)都将耗费更多的时间。这是因为,当数据发生变动时,MongoDB不仅要更新文档,还要更新集合上的所有索引。因此,MongoDB限制每个集合上最多只能有64个索引。通常,在一个特定的集合上,不应该拥有两个以上的索引。于是,挑选合适的字段建立索引非常重要。

6.2 复合索引简介

索引的值是按一定顺序排列的,因此,使用索引键对文档进行排序非常快。然而,只有在首先使用索引键进行排序时,索引才有用。例如,在下面的排序里,“username”上的索引没什么作用:

db.users.find({}).sort({'age':1, 'username':1})

这里先根据“age”排序再根据“username”排序,所以“username”在这里发挥的作用并不大。为了优化这个排序,可能需要在“age”和“username”上建立索引:

db.users.ensureIndex({'age':1, 'username':1})

这就建立了一个复合索引

6.3 复合索引键的方向

如果需要在两个(或者更多)查询条件上进行排序,可能需要让索引键的方向不同。例如,假设我们要根据年龄从小到大,用户名从Z到A对上面的集合进行排序。对于这个问题,之前的升序索引变得不再高效:每一个年龄分组内都是按照“username”升序排列的,是A到Z,不是Z到A。对于按“age”升序排列按“username”降序排列这样的需求来说,用上面的索引得到的数据的顺序没什么用。为了在不同方向上优化这个复合排序,需要使用与方向相匹配的索引。在这个例子中,可以使用{“age”:1,“username”:-1},它会以下面的方式组织数据:

[21,"user999977"]>0xe57bf737
[21,"user999954"]>0x8bffa512
[21,"user999902"]>0x9e1447d1
[21,"user999900"]>0x3a6a8426
[21,"user999874"]>0xc353ee06
...
[30,"user999936"]>0x7f39a81a
[30,"user999850"]>0xa979e136
[30,"user999775"]>0x5de6b77a
...
[30,"user100324"]>0xe14f8e4d
[30,"user100140"]>0x0f34d446
[30,"user100050"]>0x223c35b1

年龄按照从年轻到年长顺序排列,在每一个年龄分组中,用户名是从Z到A排列的(对于我们的用户名来说,也可以说是按照“9”到“0”排列的)。

如果应用程序同时需要按照{“age”:1,“username”:1}优化排序,我们还需要创建一个这个方向上的索引。至于索引使用的方向,与排序方向相同就可以了。注意,相互反转(在每个方向都乘以1)的索引是等价的:{“age”:1,“username”:-1}适用的查询与{“age”:-1,“username”:1}是完全一样的。

只有基于多个查询条件进行排序时,索引方向才是比较重要的。如果只是基于单一键进行排序,MongoDB可以简单地从相反方向读取索引。例如,如果有一个基于{“age”:-1}的排序和一个基于{“age”:1}的索引,MongoDB会在使用索引时进行优化,就如同存在一个{“age”:-1}索引一样(所以不要创建两个这样的索引!)。只有在基于多键排序时,方向才变得重要。

6.4 索引对象和数组

MongoDB允许深入文档内部,对嵌套字段和数组建立索引。嵌套对象和数组字段可以与复合索引中的顶级字段一起使用,虽然它们比较特殊,但是大多数情况下与“正常”索引字段的行为是一致的。

1. 索引嵌套文档

可以在嵌套文档的键上建立索引,方式与正常的键一样。如果有这样一个集合,其中的第一个文档表示一个用户,可能需要使用嵌套文档来表示每个用户的位置:

{"username":"sid",
   "loc":{
     "ip":"1.2.3.4",
     "city":"Springfield",
     "state":"NY"
   }
}

需要在“loc”的某一个子字段(比如“loc.city”)上建立索引,以便提高这个字段的查询速度:

db.users.ensureIndex({"loc.city":1})

可以用这种方式对任意深层次的字段建立索引,比如你可以在“x.y.z.w.a.b.c”上建立索引。

注意:对嵌套文档本身(“loc”)建立索引,与对嵌套文档的某个字段(“loc.city”)建立索引是不同的。对整个子文档建立索引,只会提高整个子文档的查询速度。

2. 索引数组

也可以对数组建立索引,这样就可以高效地搜索数组中的特定元素。假如有一个博客文章的集合,其中每个文档表示一篇文章。每篇文章都有一个“comments”字段,这是一个数组,其中每个元素都是一个评论子文档。如果想要找出最近被评论次数最多的博客文章,可以在博客文章集合中嵌套的“comments”数组的“date”键上建立索引:

db.blog.ensureIndex({"comments.date":1})

对数组建立索引,实际上是对数组的每一个元素建立一个索引条目,所以如果一篇文章有20条评论,那么它就拥有20个索引条目。因此数组索引的代价比单值索引高:对于单次插入、更新或者删除,每一个数组条目可能都需要更新(可能有上千个索引条目)。

与上面说到的“loc”的例子不同,无法将整个数组作为一个实体建立索引:对数组建立索引,实际上是对数组中的每个元素建立索引,而不是对数组本身建立索引。在数组上建立的索引并不包含任何位置信息:无法使用数组索引查找特定位置的数组元素,比如“comments.4”。

少数特殊情况下,可以对某个特定的数组条目进行索引,比如:

db.blog.ensureIndex({"comments.10.votes":1})

然而,只有在精确匹配第11个数组元素时这个索引才有用(数组下标从0开始)。一个索引中的数组字段最多只能有一个。这是为了避免在多键索引中索引条目爆炸性增长:每一对可能的元素都要被索引,这样导致每个文档拥有n*m个索引条目。假如有一个{“x”:1,“y”:1}上的索引:

>//x是一个数组——这是合法的
>db.multi.insert({"x":[1,2,3],"y":1})
>
>//y是一个数组——这也是合法的
>db.multi.insert({"x":1,"y":[4,5,6]})
>
>//x和y都是数组——这是非法的!
>db.multi.insert({"x":[1,2,3],"y":[4,5,6]})
cannotindexparallelarrays[y][x]

如果MongoDB要为上面的最后一个例子创建索引,它必须要创建这么多索引条目:{“x”:1,“y”:4}、{“x”:1,“y”:5}、{“x”:1,“y”:6}、{“x”:2,“y”:4}、{“x”:2,“y”:5},{“x”:2,“y”:6}、{“x”:3,“y”:4}、{“x”:3,“y”:5}和{“x”:3,“y”:6}。尽管这些数组只有3个元素。

6.5 索引基数

基数(cardinality)就是集合中某个字段拥有不同值的数量。有一些字段,比如“gender”或者“newsletteroptout”,可能只拥有两个可能的值,这种键的基数就是非常低的。另外一些字段,比如“username”或者“email”,可能集合中的每个文档都拥有一个不同的值,这类键的基数是非常高的。当然也有一些介于两者之间的字段,比如“age”或者“zipcode”。

通常,一个字段的基数越高,这个键上的索引就越有用。这是因为索引能够迅速将搜索范围缩小到一个比较小的结果集。对于低基数的字段,索引通常无法排除掉大量可能的匹配。

假设我们在“gender”上有一个索引,需要查找名为Susan的女性用户。通过这个索引,只能将搜索空间缩小到大约50%,然后要在每个单独的文档中查找“name”为“Susan”的用户。反过来,如果在“name”上建立索引,就能立即将结果集缩小到名为“Susan”的用户,这样的结果集非常小,然后就可以根据性别从中迅速地找到匹配的文档了。

一般说来,应该在基数比较高的键上建立索引,或者至少应该把基数较高的键放在复合索引的前面(低基数的键之前)。

6.6 索引管理

1. 创建索引

db.col.createIndex({"索引名称":1})

2. 查看集合索引

db.col.getIndexes()

3. 查看集合索引大小

db.col.totalIndexSize()

4. 删除集合所有索引

db.col.dropIndexes()

5. 删除集合指定索引

db.col.dropIndex("索引名称")

7. 聚合

7.1 管道操作aggregate

MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。有点类似sql语句中的 count(*)。

MongoDB中聚合的方法使用aggregate()。

>db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

实例

集合中的数据如下:

{
   _id: ObjectId(7df78ad8902c)
   title: 'MongoDB Overview',
   description: 'MongoDB is no sql database',
   by_user: 'w3cschool.cn',
   url: 'http://www.w3cschool.cn',
   tags: ['mongodb', 'database', 'NoSQL'],
   likes: 100
},
{
   _id: ObjectId(7df78ad8902d)
   title: 'NoSQL Overview',
   description: 'No sql database is very fast',
   by_user: 'w3cschool.cn',
   url: 'http://www.w3cschool.cn',
   tags: ['mongodb', 'database', 'NoSQL'],
   likes: 10
},
{
   _id: ObjectId(7df78ad8902e)
   title: 'Neo4j Overview',
   description: 'Neo4j is no sql database',
   by_user: 'Neo4j',
   url: 'http://www.neo4j.com',
   tags: ['neo4j', 'database', 'NoSQL'],
   likes: 750
},

现在我们通过以上集合计算每个作者所写的文章数,使用aggregate()计算结果如下:

> db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}])
{
   "result" : [
      {
         "_id" : "w3cschool.cn",
         "num_tutorial" : 2
      },
      {
         "_id" : "Neo4j",
         "num_tutorial" : 1
      }
   ],
   "ok" : 1
}

以上实例类似sql语句: select by_user, count(*) from mycol group by by_user

在上面的例子中,我们通过字段by_user字段对数据进行分组,并计算by_user字段相同值的总和。

下表展示了一些聚合的表达式:

表达式

描述

$sum

计算总和。

$avg

计算平均值

$min

获取集合中所有文档对应值得最小值。

$max

获取集合中所有文档对应值得最大值。

$push

在结果文档中插入值到一个数组中。

$addToSet

在结果文档中插入值到一个数组中,但不创建副本。

$first

根据资源文档的排序获取第一个文档数据。

$last

根据资源文档的排序获取最后一个文档数据


管道的概念

管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。

MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。

这里我们介绍一下聚合框架中常用的几个操作:

  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。

  • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。

  • $limit:用来限制MongoDB聚合管道返回的文档数。

  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。

  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。

  • $group:将集合中的文档分组,可用于统计结果。

  • $sort:将输入文档排序后输出。

  • $geoNear:输出接近某一地理位置的有序文档。

管道操作符实例

1、$project实例

db.article.aggregate(
    { $project : {
        title : 1 ,
        author : 1 ,
    }}
 );

这样的话结果中就只还有_id,tilte和author三个字段了,默认情况下_id字段是被包含的,如果要想不包含_id话可以这样:

db.article.aggregate(
    { $project : {
        _id : 0 ,
        title : 1 ,
        author : 1
    }});

2.$match实例

db.articles.aggregate( [
                        { $match : { score : { $gt : 70, $lte : 90 } } },
                        { $group: { _id: null, count: { $sum: 1 } } }
                       ] );

\(match用于获取分数大于70小于或等于90记录,然后将符合条件的记录送到下一阶段\)group管道操作符进行处理。

3.$skip实例

db.article.aggregate(
    { $skip : 5 });

经过$skip管道操作符处理后,前五个文档被“过滤”掉。

7.2 MapReduce

MapReduce基本语法

db.runCommand( {
     mapReduce: <string>,
     map: <string or JavaScript>,
     reduce: <string or JavaScript>,
     finalize: <string or JavaScript>,
     out: <output>,
     query: <document>,
     sort: <document>,
     limit: <number>,
     scope: <document>,
     jsMode: <boolean>,
     verbose: <boolean>,
     bypassDocumentValidation: <boolean>,
     collation: <document>,
     writeConcern: <document>,
     comment: <any>
} )

参数说明:

  • Mapreduce:要操作的目标集合

  • Map:映射函数(生成键值对序列,作为reduce函数参数)

  • Reduce:统计函数

  • Query:目标记录过滤

  • Sort:目标记录排序

  • Limit:限制目标记录数量

  • Out:统计结果存放集合(不指定使用临时集合,在客户端断开后自动删除)

  • Keeptemp:是否保留临时集合

  • Finalize:最终处理函数(对reduce返回结果进行最终整理后存入结果集合)

  • Scope:向map、reduce、finalize导入外部变量

  • jsMode说明:为false时 BSON–>JS–>map–>BSON–>JS–>reduce–>BSON,可处理非常大的mapreduce,为true时 BSON–>js–>map–>reduce–>BSON

  • Verbose:显示详细的时间统计信息

行查询的步骤

  • MapReduce对指定的集合Collection进行查询

  • 对A的结果集进行mapper方法采集

  • 对B的结果执行finalize方法处理

  • 最终结果集输出到临时Collection中

  • 断开连接,临时Collection删除或保留

插入样例数据

db.orders.insertMany([
   { _id: 1, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-01"), price: 25, items: [ { sku: "oranges", qty: 5, price: 2.5 }, { sku: "apples", qty: 5, price: 2.5 } ], status: "A" },
   { _id: 2, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-08"), price: 70, items: [ { sku: "oranges", qty: 8, price: 2.5 }, { sku: "chocolates", qty: 5, price: 10 } ], status: "A" },
   { _id: 3, cust_id: "Busby Bee", ord_date: new Date("2020-03-08"), price: 50, items: [ { sku: "oranges", qty: 10, price: 2.5 }, { sku: "pears", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 4, cust_id: "Busby Bee", ord_date: new Date("2020-03-18"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 5, cust_id: "Busby Bee", ord_date: new Date("2020-03-19"), price: 50, items: [ { sku: "chocolates", qty: 5, price: 10 } ], status: "A"},
   { _id: 6, cust_id: "Cam Elot", ord_date: new Date("2020-03-19"), price: 35, items: [ { sku: "carrots", qty: 10, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 7, cust_id: "Cam Elot", ord_date: new Date("2020-03-20"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 8, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 75, items: [ { sku: "chocolates", qty: 5, price: 10 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 9, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 55, items: [ { sku: "carrots", qty: 5, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 }, { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 10, cust_id: "Don Quis", ord_date: new Date("2020-03-23"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" }
])

计算每个客户的总消费

orders集合进行map-reduce操作,对cust_id进行分组,并计算每个cust_id对应的price总和

  1. 定义map函数来处理每个输入文档:

  • 在函数中,this指的是map-reduce正在处理的文档

  • 该函数将映射pricecust_id每个文档,并生成cust_idprice

var mapFunction1 = function() {
   emit(this.cust_id, this.price);
};
  1. 定义对应的reduce函数,包含两个参数keyCustIdvaluesPrices

  • valuesPrices是一个数组,其元素是由map函数处理后,根据cust_id分组的price序列

  • 该函数处理valuesPrices数组,计算出总和

var reduceFunction1 = function(keyCustId, valuesPrices) {
   return Array.sum(valuesPrices);
};
  1. orders集合执行map-reduce操作,mapFunction1作为map函数,reduceFunction1作为reduce函数

db.orders.mapReduce(
   mapFunction1,
   reduceFunction1,
   { out: "map_reduce_example" }
)

操作将结果输出到名为 map_reduce_example的集合。如果map_reduce_example集合已经存在,则该操作将用此map-reduce操作的结果替换。

  1. 查询map_reduce_example集合,验证结果

db.map_reduce_example.find().sort({ _id: 1 })

操作返回以下文档:

{ "_id" : "Ant O. Knee", "value" : 95 }
{ "_id" : "Busby Bee", "value" : 125 }
{ "_id" : "Cam Elot", "value" : 60 }
{ "_id" : "Don Quis", "value" : 155 }

聚集替代

使用管道操作符也可以实现上述map-reduce功能

db.orders.aggregate([
   { $group: { _id: "$cust_id", value: { $sum: "$price" } } },
   { $out: "agg_alternative_1" }
])

用每个项目的平均数量计算订单和总数量

  1. 定义map函数

var mapFunction2 = function() {
    for (var idx = 0; idx < this.items.length; idx++) {
       var key = this.items[idx].sku;
       var value = { count: 1, qty: this.items[idx].qty };

       emit(key, value);
    }
};
  1. 定义reduce函数

var reduceFunction2 = function(keySKU, countObjVals) {
   reducedVal = { count: 0, qty: 0 };

   for (var idx = 0; idx < countObjVals.length; idx++) {
       reducedVal.count += countObjVals[idx].count;
       reducedVal.qty += countObjVals[idx].qty;
   }

   return reducedVal;
};
  1. 定义finalize函数,对reduce的返回值做最后处理

var finalizeFunction2 = function (key, reducedVal) {
  reducedVal.avg = reducedVal.qty/reducedVal.count;
  return reducedVal;
};
  1. 执行map-reduce操作

db.orders.mapReduce(
   mapFunction2,
   reduceFunction2,
   {
     out: { merge: "map_reduce_example2" },
     query: { ord_date: { $gte: new Date("2020-03-01") } },
     finalize: finalizeFunction2
   }
 );

此操作使用query字段选择仅ord_date大于等于new Date(“2020-03-01”)的文档。然后将结果输出到map_reduce_example2集合。

  1. 查询map_reduce_example2集合,验证结果

db.map_reduce_example2.find().sort({ _id: 1 })

操作返回以下文档:

{ "_id" : "apples", "value" : { "count" : 3, "qty" : 30, "avg" : 10 } }
{ "_id" : "carrots", "value" : { "count" : 2, "qty" : 15, "avg" : 7.5 } }
{ "_id" : "chocolates", "value" : { "count" : 3, "qty" : 15, "avg" : 5 } }
{ "_id" : "oranges", "value" : { "count" : 6, "qty" : 58, "avg" : 9.666666666666666 } }
{ "_id" : "pears", "value" : { "count" : 1, "qty" : 10, "avg" : 10 } }

聚集替代

使用管道操作符也可以实现上述map-reduce功能

db.orders.aggregate( [
   { $match: { ord_date: { $gte: new Date("2020-03-01") } } },
   { $unwind: "$items" },
   { $group: { _id: "$items.sku", qty: { $sum: "$items.qty" }, orders_ids: { $addToSet: "$_id" } }  },
   { $project: { value: { count: { $size: "$orders_ids" }, qty: "$qty", avg: { $divide: [ "$qty", { $size: "$orders_ids" } ] } } } },
   { $merge: { into: "agg_alternative_3", on: "_id", whenMatched: "replace",  whenNotMatched: "insert" } }
] )

8. 副本集

8.1 主从复制

主从复制是 MongoDB 最早使用的复制方式, 该复制方式易于配置,并且可以支持任意数量的从节点服务器,与使用单节点模式相比有如下优点:

  • 在从服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性

  • 可配置读写分离,主节点负责写操作,从节点负责读操作,将读写压力分开,提高系统的稳定性

MongoDB 的主从复制至少需要两个服务器或者节点。其中一个是主节点,负责处理客户端请求,其它的都是从节点,负责同步主节点的数据。

主节点记录在其上执行的所有写操作,从节点定期轮询主节点获取这些操作,然后再对自己的数据副本执行这些操作。由于和主节点执行了相同的操作,从节点就能保持与主节点的数据同步。

主节点的操作记录称为oplog(operation log),它被存储在 MongoDB 的 local 数据库中。oplog 中的每个文档都代表主节点上执行的一个操作。需要重点强调的是oplog只记录改变数据库状态的操作。比如,查询操作就不会被存储在oplog中。这是因为oplog只是作为从节点与主节点保持数据同步的机制。

然而,主从复制并非生产环境下推荐的复制方式,主要原因如下:

  • 灾备都是完全人工的:如果主节点发生故障失败,管理员必须关闭一个从服务器,然后作为主节点重新启动它。然后应用程序必须重新配置连接新的主节点。

  • 数据恢复困难:因为oplog只在主节点存在,故障失败需要在新的服务器上创建新的oplog,这意味着任意存在的节点需要重新从新的主节点同步oplog。

因此,在新版本的MongoDB中已经不再支持使用主从复制这种复制方式了,取而代之的是使用副本集复制方式。

8.2 MongoDB副本集

MongoDB副本集(Replica Set)其实就是具有自动故障恢复功能的主从集群,和主从复制最大的区别就是在副本集中没有固定的主节点;整个副本集会选出一个节点作为主节点,当其挂掉后,再在剩下的从节点中选举一个节点成为新的主节点,在副本集中总有一个主节点(primary)和一个或多个备份节点(secondary)。

客户端连接到整个副本集,不关心具体哪一台机器是否挂掉。主服务器负责整个副本集的读写,副本集定期同步数据备份,一但主节点挂掉,副本节点就会选举一个新的主服务器,这一切对于应用服务器不需要关心。

除了primary和secondary之外,副本集中的节点还可以是以下角色:

成为primary

对客户端可见

参与投票

延迟同步

复制数据

Default

Secondary-Only

Hidden

Delayed

Arbiters

Non-Voting

  • 主节点(Primary) 接收所有的写请求,然后把修改同步到所有Secondary。一个Replica Set只能有一个Primary节点,当Primary挂掉后,其他Secondary或者Arbiter节点会重新选举出来一个主节点。 默认读请求也是发到Primary节点处理的,可以通过修改客户端连接配置以支持读取Secondary节点。

  • 副本节点(Secondary) 与主节点保持同样的数据集。当主节点挂掉的时候,参与选主。

  • 仲裁者(Arbiter) 不保有数据,不参与选主,只进行选主投票。使用Arbiter可以减轻数据存储的硬件需求,Arbiter几乎没什么大的硬件资源需求,但重要的一点是,在生产环境下它和其他数据节点不要部署在同一台机器

8.3 副本集和主从复制的区别

其实副本集(Replica Set)是主从复制的高级形式。主从复制实现了数据备份+读扩展,但是master一旦down掉,需要手动启动slave。副本集在此基础上实现了备份自动重启的功能,也就是某一台slave会挺身而出,担当起master的职责。所以有三个角色:PrimarySecondaryArbiter

副本集特征:

  • N 个节点的集群

  • 任何节点可作为主节点

  • 所有写入操作都在主节点上

  • 自动故障转移

  • 自动恢复

8.4 副本集架构

官方推荐的副本集最小配置需要有三个节点:一个主节点接收和处理所有的写操作,两个备份节点通过复制主节点的操作来对主节点的数据进行同步备份。

当开发一个副本集架构时要注意下面的因素:

  1. 确保副本集的成员总能选出一个primary。运行奇数个成员或者运行一个仲裁者(arbiter)+偶数个成员。

  2. 分布在不同地理位置的成员,知道“群体”的成员在任意网络分区中的情况。试图确保在主数据中心的成员中选举出primary。

  3. 考虑副本集中包含hidden或者delayed成员用于支持专用功能,如备份、reporting和测试。

  4. 考虑保留一或者两个位于其他数据中心的成员,同时通过配置确保其不会成为primary。

  5. 使用replica set tags创建定制的写规则以确保应用能够控制写操作成功的门限值。使用写规则确保操作在返回成功之前将操作传递给指定的数据中心或不同功能的机器。

8.5 部署策略

如果副本集中的成员多于三个,则需要遵照下面的架构条件:

  • 集合中有奇数个参与投票的成员。如果有偶数个投票成员,则部署一个仲裁者将个数变为奇数。

  • 集合中同一时刻不多于7个参与投票的成员

  • 如果不想让某些成员在故障切换时成为primary,则将它们的优先级设为0。

  • 集合的多数成员运行在主要的数据中心

8.6 副本集常见操作
rs.status()   //查看成员的运行状态等信息

rs.config()    //查看配置信息

rs.slaveOk()  //允许在SECONDARY节点上进行查询操作,默认从节点不具有查询功能

rs.isMaster()  //查询该节点是否是主节点

rs.add({})   //添加新的节点到该副本集中

rs.remove()   //从副本集中删除节点

9. 分片

MongoDB的分片机制允许创建一个包含许多台机器(分片)的集群,将数据子集分散在集群中,每个分片维护着一个数据集合的子集。与单机服务器和副本集相比,使用集群架构可以使应用程序具有更大的数据处理能力。

和MySQL分区方案相比,MongoDB的最大区别在于它几乎能自动完成所有事情,只要告诉MongoDB要分配数据,它就能自动维护数据在不同服务器之间的均衡。

注意:分片与副本集(复制)是不同的。复制是让多台服务器都拥有同样的数据副本,每一台服务器都是其他服务器的镜像,而每一个分片都有其他分片拥有不同的数据子集。

9.1 分片的目的

高数据量和吞吐量的数据库应用会对单机的性能造成较大压力,大的查询量会将单机的CPU耗尽,大的数据量对单机的存储压力较大,最终会耗尽系统的内存而将压力转移到磁盘IO上。

为了解决这些问题,有两个基本的方法:

  • 垂直扩展:增加更多的CPU和存储资源来扩展容量。

  • 水平扩展:将数据集分布在多个服务器上。水平扩展即分片。

9.2 分片的时机

决定何时分片是一个值得权衡的问题。通常不必太早分片,因为分片不仅会增加部署的操作复杂度,还要求做出设计决策,而该决策以后很难再改。另外最好也不要在系统运行太久之后再分片,因为在一个过载的系统上不停机进行分片是非常困难的。

通常,分片用来:

  • 增加可用RAM

  • 增加可用磁盘空间

  • 减轻单台服务器的负载

  • 处理单个mongod无法承受的吞吐量

因此,良好的监控对于决定应何时分片是十分重要的,必须认真对待其中每一项。由于人们往往过于关注改进其中一个指标,所以应弄明白到底哪一项指标对自己的部署最为重要,并提前做好何时分片以及如何分片的计划。随着不断增加分片数量,系统性能大致会呈线性增长。但是,如果从一个未分片的系统转换为只有几个分片的系统,性能通常会有所下降。由于迁移数据、维护元数据、路由等开销,少量分片的系统与未分片的系统相比,通常延迟更大,吞吐量甚至可能会更小。因此,至少应该创建3个或以上的分片。

9.3 分片的原理

MongoDB通过配置分片集群来支持分片,一个分片集群包括以下几个组件:

img

img

  • Shard:

    用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个replica set承担,防止主机单点故障

  • Config Server:

    mongod实例,存储了整个 ClusterMetadata,其中包括 chunk信息。

  • mongos:

    前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。

Mongos本身并不持久化数据,Sharded cluster所有的元数据都会存储到Config Server,而用户的数据会分散存储到各个Shard。Mongos启动后,会从配置服务器加载元数据,开始提供服务,将用户的请求正确路由到对应的碎片。

9.4 分片的优势
  1. mongos对集群进行抽象,让集群“不可见”

    对于一个读写操作,mongos 需要知道应该将其路由到哪个复制集上,mongos通过将片键空间划分为若干个区间,计算出一个操作的片键的所属区间对应的复制集来实现路由。

  2. 保证集群总是可读写

    MongoDB通过多种途径来确保集群的可用性和可靠性。将MongoDB的分片和复制功能结合使用,在确保数据分片到多台服务器的同时,也确保了每分数据都有相应的备份,这样就可以确保有服务器换掉时,其他的从库可以立即接替坏掉的部分继续工作。

  3. 使集群易于扩展

    当系统需要更多的空间和资源的时候,MongoDB使我们可以按需方便的扩充系统容量。

10.身份验证

10.1 启用访问控制

在MongoDB部署时启用访问控制会强制执行身份验证,要求用户表明身份。当MongoDB部署时启用了访问控制后,用户只能执行由其角色限定的操作。

在启用访问控制之前,应该创建一个用户,该用户可以在启用访问控制后创建用户并为用户分配角色。然后,这个用户管理员将用于创建和维护其他用户和角色,因此需要分配一个合适的角色(具有 userAdminuserAdminAnyDatabase角色)来支持。如果你不创建此管理用户,则在启用访问控制时将无法登录或创建新用户和角色。

以下过程首先将用户管理员添加到没有访问控制的情况下运行的MongoDB实例,然后启用访问控制。

1. 在没有访问控制的情况下启动MongoDB实例

mongod --port 27017 --dbpath /var/lib/mongodb

2. 连接到实例

mongo --port 27017

3. 创建用户管理员

use admin
db.createUser(
  {
    user: "myUserAdmin",
    pwd: passwordPrompt(), // or cleartext password
    roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
  }
)

4. 使用访问控制重启MongoDB实例

  • 使用命令行启动,添加--auth命令

mongod --auth --port 27017 --dbpath /var/lib/mongodb
  • 使用配置文件,添加 security.authorization配置

security:
    authorization: enabled

5. 以用户管理员身份连接并进行身份验证

  • 使用命令行验证方式

mongo --port 27017  --authenticationDatabase "admin" -u "myUserAdmin" -p
  • 连接后身份验证

mongo --port 27017
use admin
db.auth("myUserAdmin", passwordPrompt()) // or cleartext password

6. 根据部署需要,创建其他用户

以下操作是在test数据库,创建一个myTester用户。该用户具有对test数据库的readWrite权限以及reporting数据库的read权限

use test
db.createUser(
  {
    user: "myTester",
    pwd:  passwordPrompt(),   // or cleartext password
    roles: [ { role: "readWrite", db: "test" },
             { role: "read", db: "reporting" } ]
  }
)

7. 以myTester身份连接到MongoDB实例

  • 连接过程中验证

mongo --port 27017 -u "myTester" --authenticationDatabase "test" -p
  • 连接后验证

mongo --port 27017
use test
db.auth("myTester", passwordPrompt())  // or cleartext password
10.2 内置角色
1. 数据库用户角色
  • read:只读数据权限

  • readWrite:读写数据权限

2. 数据库管理角色
  • dbAdmin: 在当前db中执行管理操作的权限

  • dbOwner: 在当前db中执行任意操作

  • userAdmin: 在当前db中管理user的权限

3. 备份和还原角色
  • backup

  • restore

4. 所有数据库角色
  • readAnyDatabase: 在所有数据库上都有读取数据的权限

  • readWriteAnyDatabase: 在所有数据库上都有读写数据的权限

  • userAdminAnyDatabase: 在所有数据库上都有管理user的权限

  • dbAdminAnyDatabase: 管理所有数据库的权限

5. 集群管理
  • clusterAdmin: 管理机器的最高权限

  • clusterManager: 管理和监控集群的权限

  • clusterMonitor: 监控集群的权限

  • hostManager: 管理Server

6. 超级权限
  • root: 超级用户

10.3 用户管理

Name

Description

db.auth()

Authenticates a user to a database.

db.changeUserPassword()

Changes an existing user’s password.

db.createUser()

Creates a new user.

db.dropUser()

Removes a single user.

db.dropAllUsers()

Deletes all users associated with a database.

db.getUser()

Returns information about the specified user.

db.getUsers()

Returns information about all users associated with a database.

db.grantRolesToUser()

Grants a role and its privileges to a user.

10.4 权限管理

Name

Description

db.createRole()

Creates a role and specifies its privileges.

db.dropRole()

Deletes a user-defined role.

db.dropAllRoles()

Deletes all user-defined roles associated with a database.

db.getRole()

Returns information for the specified role.

db.getRoles()

Returns information for all the user-defined roles in a database.

db.grantPrivilegesToRole()

Assigns privileges to a user-defined role.

db.revokePrivilegesFromRole()

Removes the specified privileges from a user-defined role.

db.grantRolesToRole()

Specifies roles from which a user-defined role inherits privileges.

db.revokeRolesFromRole()

Removes inherited roles from a role.

db.updateRole()

Updates a user-defined role.

11. 服务器管理

11.1 常用配置项

执行mongod程序即可启动MongoDB服务器,mongod在启动时可使用许多可配置选项,在命令行中运行mongod --help可列出这些选项。下列选项十分常用,需着重注意。

  • –dbpath

    使用此选项可指定一个目录为数据目录。其默认值为/data/db/(在Windows中则为MongoDB可执行文件所在磁盘卷中的:raw-latex:data:raw-latex:`\db目录`)。机器上的每个mongod进程都需要属于自己的数据目录,即若在同一机器上运行三个mongod实例,则需三个独立的数据目录。mongod启动时,会在其数据目录中创建一个mongod.lock文件,以阻止其他mongod进程使用此数据目录。若尝试启动另一个使用相同数据目录的MongoDB服务器,则会出现错误提示:"Unabletoacquirelockforlockfilepath:/data/db/mongod.lock."

  • –port

    此选项用以指定服务器监听的端口号。mongod默认占用27017端口,除其他mongod进程外,其余程序不会使用此端口。若要在同一机器上运行多个mongod进程,则需为它们指定不同的端口。若尝试在已被占用的端口启动mongod,则会出现错误提示:"Addressalreadyinuseforsocket:0.0.0.0:27017"

  • –fork

    启用此选项以调用fork创建子进程,在后台运行MongoDB。

    首次启动mongod而数据目录为空时,文件系统需几分钟时间分配数据库文件。预分配结束,mongod可接收连接后,父进程才会继续运行。因此,fork可能会发生挂起。可查看日志中的最新记录得知正在进行的操作。启用--fork选项时,必须同时启用--logpath选项。

  • –logpath

    使用此选项,所有输出信息会被发送至指定文件,而非在命令行上输出。假设我们拥有该目录的写权限,若指定文件不存在,启用该选项后则会自动生成一个文件。若指定日志文件已存在,选项启用后则会覆盖掉该文件,并清除所有旧的日志条目。如需保留旧日志,除--logpath选项外,强烈建议使用--logappend选项。

  • –directoryperdb

    启用该选项可将每个数据库存放在单独的目录中。我们可由此按需将不同的数据库挂载到不同的磁盘上。该选项一般用于将本地数据库或副本放置于单独的磁盘上,或在磁盘空间不足时将数据库移动至其他磁盘。也可将频繁操作的数据库挂载到速度较快的磁盘上,而将不常用的数据库放到较慢的磁盘上。总之该选项能使我们在今后更加灵活地操作数据库。

  • –config

    额外加载配置文件,未在命令行中指定的选项将使用配置文件中的参数。该选项通常用于确保每次重新启动时的选项都是一样的。

  • –bind_ip

    指定MongoDB监听的接口。我们通常将其设置为一个内部IP地址,从而保证应用服务器和集群中其他成员的访问,同时拒绝外网的访问。如MongoDB与应用服务器运行于同一台机器上,则可将其设为localhost。但配置服务器和分片需要其他机器的访问,所以不应设为localhost。

  • –nohttpinterface

    MongoDB启动时,默认在端口1000启动一个微型的HTTP服务器。该服务器可提供一些系统信息,但这些信息均可在其他地方找到。对于一个可能只需通过SSH访问的机器,没有必要将这些信息暴露在外网上。

    除非正在进行开发,否则请关闭此选项。

  • –nounixsocket

    如不打算使用UNIXsocket来进行连接,则可禁用此选项。只有在本地,即应用服务器和MongoDB运行在同一台机器上时,才能使用socket进行连接。

  • –noscripting

    该选项完全禁止服务器端JavaScript脚本的运行。大多数报告的MongoDB安全问题都与JavaScript有关。如程序允许的话,禁止JavaScript通常会更安全一些。

    一些shell中的辅助函数依赖于服务器端的JavaScript,尤其是sh.status()。在一台禁止了JavaScript的服务器上运行这些辅助函数时,会出现错误提示。

MongoDB支持从文件中读取配置信息。当使用的选项很多,或自动化启动任务时,使用配置文件就十分实用。使用-f-config标记,告知服务器使用配置文件。例如,运行mongod --config ~/.mongodb.conf,从而使用~/.mongodb.conf作为配置文件。

11.2 备份与恢复
1. 备份

在Mongodb中我们使用mongodump命令来备份MongoDB数据。该命令可以导出所有数据到指定目录中。

mongodump命令可以通过参数指定导出的数据量级转存的服务器。

语法

mongodump命令脚本语法如下:

>mongodump -h dbhost -d dbname -o dbdirectory
  • -h:

    MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017

  • -d:

    需要备份的数据库实例,例如:test

  • -o:

    备份的数据存放位置,例如:c:\data\dump,当然该目录需要提前建立,在备份完成后,系统自动在dump目录下建立一个test目录,这个目录里面存放该数据库实例的备份数据。

mongodump 命令可选参数列表如下:

2.恢复

语法

mongorestore命令脚本语法如下:

>mongorestore -h <hostname><:port> -d dbname <path>
  • --host <:port>, -h <:port>

    MongoDB所在服务器地址,默认为: localhost:27017

  • --db , -d

    需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2

  • --drop

    恢复的时候,先删除当前数据,然后恢复备份的数据。就是说,恢复后,备份后添加修改的数据都会被删除,慎用哦!

  • <path>

    mongorestore 最后的一个参数,设置备份数据所在位置,例如:c:\data\dump\test

    你不能同时指定 <path>--dir 选项,--dir也可以设置备份目录。

  • --dir

    指定备份的目录

    你不能同时指定 <path>--dir 选项。

11.3 监控

MongoDB中提供了mongostat 和 mongotop 两个命令来监控MongoDB的运行情况。

1. mongostat 命令

mongostat是mongodb自带的状态检测工具,在命令行下使用。它会间隔固定时间获取mongodb的当前运行状态,并输出。如果你发现数据库突然变慢或者有其他问题的话,你第一手的操作就考虑采用mongostat来查看mongo的状态。

2. mongotop 命令

mongotop也是mongodb下的一个内置工具,mongotop提供了一个方法,用来跟踪一个MongoDB的实例,查看哪些大量的时间花费在读取和写入数据。 mongotop提供每个集合的水平的统计数据。默认情况下,mongotop返回值的每一秒。


参考地址

MongoDB 官网地址:https://www.mongodb.com/

MongoDB官方文档:https://docs.mongodb.com/manual/

w3cschool:https://www.w3cschool.cn/mongodb/

第七章:Linux笔记

7.1 dd命令

1. dd命令

dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。

注意:指定数字的地方若以下列字符结尾,则乘以相应的数字:b=512;c=1;k=1024;w=2

参数注释:

  • if=文件名:输入文件名,缺省为标准输入。即指定源文件。< if=input file >

  • of=文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >

  • ibs=bytes:一次读入bytes个字节,即指定一个块大小为bytes个字节。 obs=bytes:一次输出bytes个字节,即指定一个块大小为bytes个字节。 bs=bytes:同时设置读入/输出的块大小为bytes个字节。

  • cbs=bytes:一次转换bytes个字节,即指定转换缓冲区大小。

  • skip=blocks:从输入文件开头跳过blocks个块后再开始复制。

  • seek=blocks:从输出文件开头跳过blocks个块后再开始复制。 注意:通常只用当输出文件是磁盘或磁带时才有效,即备份到磁盘或磁带时才有效。

  • count=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数。

  • conv=conversion:用指定的参数转换文件。

    • ascii:转换ebcdic为ascii

    • ebcdic:转换ascii为ebcdic

    • ibm:转换ascii为alternate ebcdic

    • block:把每一行转换为长度为cbs,不足部分用空格填充

    • unblock:使每一行的长度都为cbs,不足部分用空格填充

    • lcase:把大写字符转换为小写字符

    • ucase:把小写字符转换为大写字符

    • swab:交换输入的每对字节

    • noerror:出错时不停止

    • notrunc:不截短输出文件

    • sync:将每个输入块填充到ibs个字节,不足部分用空(NUL)字符补齐。

2. dd应用实例

2.1 将本地的/dev/hdb整盘备份到/dev/hdd
dd if=/dev/hdb of=/dev/hdd
2.2 将/dev/hdb全盘数据备份到指定路径的image文件
dd if=/dev/hdb of=/root/image
2.3 将备份文件恢复到指定盘
dd if=/root/image of=/dev/hdb
2.4 备份/dev/hdb全盘数据,并利用gzip工具进行压缩,保存到指定路径
dd if=/dev/hdb | gzip> /root/image.gz
2.5 将压缩的备份文件恢复到指定盘
gzip -dc /root/image.gz | dd of=/dev/hdb
2.6 备份与恢复MBR

备份磁盘开始的512个字节大小的MBR信息到指定文件:

dd if=/dev/hda of=/root/image count=1 bs=512

count=1指仅拷贝一个块;bs=512指块大小为512个字节。

恢复:

dd if=/root/image of=/dev/had

将备份的MBR信息写到磁盘开始部分

2.7 备份软盘
dd if=/dev/fd0 of=disk.img count=1 bs=1440k (即块大小为1.44M)
2.8 拷贝内存内容到硬盘
dd if=/dev/mem of=/root/mem.bin bs=1024 (指定块大小为1k)
2.9 拷贝光盘内容到指定文件夹,并保存为cd.iso文件
dd if=/dev/cdrom(hdc) of=/root/cd.iso
2.10 增加swap分区文件大小

第一步:创建一个大小为256M的文件:

dd if=/dev/zero of=/swapfile bs=1024 count=262144

第二步:把这个文件变成swap文件:

mkswap /swapfile

第三步:启用这个swap文件:

swapon /swapfile

第四步:编辑/etc/fstab文件,使在每次开机时自动加载swap文件:

/swapfile swap swap default 0 0
2.11 销毁磁盘数据
dd if=/dev/urandom of=/dev/hda1

注意:利用随机的数据填充硬盘,在某些必要的场合可以用来销毁数据。

2.12 测试硬盘的读写速度
dd if=/dev/zero bs=1024 count=1000000 of=/root/1Gb.file

ddif=/root/1Gb.file bs=64k | dd of=/dev/null

通过以上两个命令输出的命令执行时间,可以计算出硬盘的读、写速度。

2.13 确定硬盘的最佳块大小:
dd if=/dev/zero bs=1024 count=1000000 of=/root/1Gb.file

dd if=/dev/zero bs=2048 count=500000 of=/root/1Gb.file

dd if=/dev/zero bs=4096 count=250000 of=/root/1Gb.file

dd if=/dev/zero bs=8192 count=125000 of=/root/1Gb.file

通过比较以上命令输出中所显示的命令执行时间,即可确定系统最佳的块大小。

2.14 修复硬盘:
dd if=/dev/sda of=/dev/sda 或dd if=/dev/hda of=/dev/hda

当硬盘较长时间(一年以上)放置不使用后,磁盘上会产生magnetic flux point,当磁头读到这些区域时会遇到困难,并可能导致I/O错误。当这种情况影响到硬盘的第一个扇区时,可能导致硬盘报废。上边的命令有可能使这些数 据起死回生。并且这个过程是安全、高效的。

2.15 利用netcat远程备份
dd if=/dev/hda bs=16065b | netcat < targethost-IP > 1234

在源主机上执行此命令备份/dev/hda

netcat -l -p 1234 | dd of=/dev/hdc bs=16065b

在目的主机上执行此命令来接收数据并写入/dev/hdc

netcat -l -p 1234 | bzip2 > partition.img

netcat -l -p 1234 | gzip > partition.img

以上两条指令是目的主机指令的变化分别采用bzip2、gzip对数据进行压缩,并将备份文件保存在当前目录。

2.16 将一个很大的视频文件中的第i个字节的值改成0x41(也就是大写字母A的ASCII值)
echo A | dd of=bigfile seek=$i bs=1 count=1 conv=notrunc

3. /dev/null和/dev/zero的区别

/dev/null,外号叫无底洞,你可以向它输出任何数据,它通吃,并且不会撑着!

/dev/zero,是一个输入设备,你可你用它来初始化文件。该设备无穷尽地提供0,可以使用任何你需要的数目——设备提供的要多的多。他可以用于向设备或文件写入字符串0。

/dev/null,它是空设备,也称为位桶(bit bucket)。任何写入它的输出都会被抛弃。如果不想让消息以标准输出显示或写入文件,那么可以将消息重定向到位桶。

4. 创建大文件

dd命令可以轻易实现创建指定大小的文件,如

dd if=/dev/zero of=test bs=1M count=1000

会生成一个1000M的test文件,文件内容为全0(因从/dev/zero中读取,/dev/zero为0源)但是这样为实际写入硬盘,文件产生速度取决于硬盘读写速度,如果欲产生超大文件,速度很慢

在某种场景下,我们只想让文件系统认为存在一个超大文件在此,但是并不实际写入硬盘

则可以

dd if=/dev/zero of=test bs=1M count=0 seek=100000

此时创建的文件在文件系统中的显示大小为100000MB,但是并不实际占用block,因此创建速度与内存速度相当

seek的作用是跳过输出文件中指定大小的部分,这就达到了创建大文件,但是并不实际写入的目的。当然,因为不实际写入硬盘,所以你在容量只有10G的硬盘上创建100G的此类文件都是可以的。

7.2 ubuntu环境下挂载新硬盘

  1. 显示硬盘及所属分区情况,在终端窗口中输入如下命令

fdisk -lu

可以看到要挂载的磁盘

  1. 对硬盘进行分区,在终端窗口中输入如下命令:

fdisk /dev/sdb

步骤如下:

  • 在Command (m for help)提示符后面输入m显示一个帮助菜单。

  • 在Command (m for help)提示符后面输入n,执行 add a new partition 指令给硬盘增加一个新分区。

  • 出现Command action时,输入p。

  • 出现Partition number(1-4)时,输入1表示只分一个区。

  • 后续指定起启柱面(cylinder)号完成分区。

  • 在Command (m for help)提示符后面输入p,显示分区表。

  • 在Command (m for help)提示符后面输入w,保存分区表。

  • 系统提示:The partition table has been altered!

  • 格式化分区

ext4 表示将分区格式化成ext4文件系统类型

mkfs.ext4 /dev/sdd1
  • 挂载分区

指定硬盘分区文件系统类型为ext4 ,同时将 /dev/sdb1 分区挂载到目录/media/sdb1

mkdir /media/sdd1

cat /etc/fstab

/dev/mapper/ubuntu--vg-root /        ext4  errors=remount-ro 0    1

/dev/mapper/ubuntu--vg-swap_1 none      swap  sw       0    0

/dev/sdb1        /media/sdb1       ext4  rw       0   0

/dev/sdc1        /media/sdc1       ext4  rw       0   0

/dev/sdd1        /media/sdd1       ext4  rw       0   0

重启服务器执行命令查看挂载效果

df -h

7.3 硬盘SMART检测参数详解

1. SMART概述

​ 要说Linux用户最不愿意看到的事情,莫过于在毫无警告的情况下发现硬盘崩溃了。诸如RAID的备份和存储技术可以在任何时候帮用户恢复数据,但为预防硬件崩溃造成数据丢失所花费的代价却是相当可观的,特别是在用户从来没有提前考虑过在这些情况下的应对措施时。

硬盘的故障一般分为两种:可预测的(predictable)和不可预测的(unpredictable)。后者偶而会发生,也没有办法去预防它,例如芯片突然失效,机械撞击等。但像电机轴承磨损、盘片磁介质性能下降等都属于可预测的情况,可以在在几天甚至几星期前就发现这种不正常的现象。

​ 对于可预测的情况,如果能通过磁盘监控技术,通过测量硬盘的几个重要的安全参数和评估他们的情况,然后由监控软件得出两种结果:“硬盘安全”或“不久后会发生故障”。那么在发生故障前,至少有足够的时间让使用者把重要资料转移到其它储存设备上。

最早期的硬盘监控技术起源于1992年,IBM在AS/400计算机的IBM 0662 SCSI 2代硬盘驱动器中使用了后来被命名为Predictive Failure Analysis(故障预警分析技术)的监控技术,它是通过在固件中测量几个重要的硬盘安全参数和评估他们的情况,然后由监控软件得出两种结果:“硬盘安全”或“不久后会发生故障”。

不久,当时的微机制造商康柏和硬盘制造商希捷、昆腾以及康纳共同提出了名为IntelliSafe的类似技术。通过该技术,硬盘可以测量自身的的健康指标并将参量值传送给操作系统和用户的监控软件中,每个硬盘生产商有权决定哪些指标需要被监控以及设定它们的安全阈值。

1995年,康柏公司将该技术方案提交到Small Form Factor(SFF)委员会进行标准化,该方案得到IBM、希捷、昆腾、康纳和西部数据的支持,1996年6月进行了1.3版的修正,正式更名为S.M.A.R.T.(Self-Monitoring Analysis And Reporting Technology),全称就是“自我检测分析与报告技术”,成为一种自动监控硬盘驱动器完好状况和报告潜在问题的技术标准。

SMART的目的是监控硬盘的可靠性、预测磁盘故障和执行各种类型的磁盘自检。如今大部分的ATA/SATA、SCSI/SAS和固态硬盘都搭载内置的SMART系统。作为行业规范,SMART规定了硬盘制造厂商应遵循的标准,满足SMART标准的条件主要包括:

1)在设备制造期间完成SMART需要的各项参数、属性的设定;

2)在特定系统平台下,能够正常使用SMART;通过BIOS检测,能够识别设备是否支持SMART并可显示相关信息,而且能辨别有效和失效的SMART信息;

3)允许用户自由开启和关闭SMART功能;

4)在用户使用过程中,能提供SMART的各项有效信息,确定设备的工作状态,并能发出相应的修正指令或警告。在硬盘及操作系统都支持SMART技术并且开启的情况下,若硬盘状态不良,SMART功能会在开机时响起警报,SMART技术能够在屏幕上显示英文警告信息:“WARNING IMMEDIATLY BACKUP YOUR DATA AND REPLACE YOUR HARD DISK DRIVE,A FAILURE MAY BE IMMINENT.”(警告:立刻备份你的数据并更换硬盘,硬盘可能失效。)

SMART功能不断从硬盘上的各个传感器收集信息,并把信息保存在硬盘的系统保留区(service area)内,这个区域一般位于硬盘0物理面的最前面几十个物理磁道,由厂商写入相关的内部管理程序。这里除了SMART信息表外还包括低级格式化程序、加密解密程序、自监控程序、自动修复程序等。用户使用的监测软件通过名为“SMART Return Status”的命令(命令代码为:B0h)对SMART信息进行读取,且不允许最终用户对信息进行修改。

smartmontools是smart的的软件包程序,由smartctl和smartd两部分工具程序组成,它们一起为Linux平台提供对磁盘退化和故障的高级警告。

2. smart信息解读

img

ID

属性ID,通常是一个1到255之间的十进制或十六进制的数字。硬盘SMART检测的ID代码以两位十六进制数表示(括号里对应的是十进制数)硬盘的各项检测参数。目前,各硬盘制造商的绝大部分SMART ID代码所代表的参数含义是一致的,但厂商也可以根据需要使用不同的ID代码,或者根据检测项目的多少增减ID代码。一般来说,以下这些检测项是必需的:

01(001) Raw_Read_Error_Rate 底层数据读取错误率

04(004) Start_Stop_Count 启动/停止计数

05(005) Reallocated_Sector_Ct 重映射扇区数

09(009) Power_On_Hours 通电时间累计,出厂后通电的总时间,一般磁盘寿命三万小时

0A(010) Spin_Retry_Count 主轴起旋重试次数(即硬盘主轴电机启动重试次数)

0B(011) Calibration_Retry_Count 磁盘校准重试次数

0C(012) Power_Cycle_Count 磁盘通电次数

C2(194) Temperature_Celsius 温度

C7(199) UDMA_CRC_Error_Count 奇偶校验错误率

C8(200) Write_Error_Rate: 写错误率

F1(241) Total_LBAs_Written:表示磁盘自出厂总共写入的的数据,单位是LBAS=512Byte

F2(242) Total_LBAs_Read:表示磁盘自出厂总共读取的数据,单位是LBAS=512Byte

ATTRIBUTE_NAME

硬盘制造商定义的属性名。,即某一检测项目的名称,是ID代码的文字解释。

FLAG

属性操作标志(可以忽略)

当前值(value)

当前值是各ID项在硬盘运行时根据实测原始数据(Raw value)通过公式计算的结果,1到253之间。253意味着最好情况,1意味着最坏情况。计算公式由硬盘厂家自定。

硬盘出厂时各ID项目都有一个预设的最大正常值,也即出厂值,这个预设的依据及计算方法为硬盘厂家保密,不同型号的硬盘都不同,最大正常值通常为100或200或253,新硬盘刚开始使用时显示的当前值可以认为是预设的最大正常值(有些ID项如温度等除外)。随着使用损耗或出现错误,当前值会根据实测数据而不断刷新并逐渐减小。因此,当前值接近临界值就意味着硬盘寿命的减少,发生故障的可能性增大,所以当前值也是判定硬盘健康状态或推测寿命的依据之一。

最差值(Worst)

最差值是硬盘运行时各ID项曾出现过的最小的value。

最差值是对硬盘运行中某项数据变劣的峰值统计,该数值也会不断刷新。通常,最差值与当前值是相等的,如果最差值出现较大的波动(小于当前值),表明硬盘曾出现错误或曾经历过恶劣的工作环境(如温度)。

临界值(Threshold)

在报告硬盘FAILED状态前,WORST可以允许的最小值。

临界值是硬盘厂商指定的表示某一项目可靠性的门限值,也称阈值,它通过特定公式计算而得。如果某个参数的当前值接近了临界值,就意味着硬盘将变得不可靠,可能导致数据丢失或者硬盘故障。由于临界值是硬盘厂商根据自己产品特性而确定的,因此用厂商提供的专用检测软件往往会跟Windows下检测软件的检测结果有较大出入。

硬盘的每项SMART信息中都有一个临界值(阈值),不同硬盘的临界值是不同的,SMART针对各项的当前值、最差值和临界值的比较结果以及数据值进行分析后,提供硬盘当前的评估状态,也是我们直观判断硬盘健康状态的重要信息。根据SMART的规定,状态一般有正常、警告、故障或错误三种状态。

SMART判定这三个状态与SMART的 Pre-failure/advisory BIT(预测错误/发现位)参数的赋值密切相关,当Pre-failure/advisory BIT=0,并且当前值、最差值远大于临界值的情况下,为正常标志。当Pre-failure/advisory BIT=0,并且当前值、最差值大于但接近临界值时,为警告标志;当Pre-failure/advisory BIT=1,并且当前值、最差值小于临界值时,为故障或错误标志

原始值(RAW_VALUE)

制造商定义的原始值,从VALUE派生。

数据值是硬盘运行时各项参数的实测值,大部分SMART工具以十进制显示数据。

数据值代表的意义随参数而定,大致可以分为三类:

1)数据值并不直接反映硬盘状态,必须经过硬盘内置的计算公式换算成当前值才能得出结果;

2)数据值是直接累计的,如Start/Stop Count(启动/停止计数)的数据是50,即表示该硬盘从出厂到现在累计启停了50次;

3)有些参数的数据是即时数,如Temperature(温度)的数据值是44,表示硬盘的当前温度是44℃。

因此,有些参数直接查看数据也能大致了解硬盘目前的工作状态。

TYPE

属性的类型(Pre-fail或Oldage)。Pre-fail类型的属性可被看成一个关键属性,表示参与磁盘的整体SMART健康评估(PASSED/FAILED)。如果任何Pre-fail类型的属性故障,那么可视为磁盘将要发生故障。另一方面,Oldage类型的属性可被看成一个非关键的属性(如正常的磁盘磨损),表示不会使磁盘本身发生故障。

UPDATED

表示属性的更新频率。Offline代表磁盘上执行离线测试的时间。

WHEN_FAILED

如果VALUE小于等于THRESH,会被设置成“FAILING_NOW”;如果WORST小于等于THRESH会被设置成“In_the_past”;如果都不是,会被设置成“-”。在“FAILING_NOW”情况下,需要尽快备份重要 文件,特别是属性是Pre-fail类型时。“In_the_past”代表属性已经故障了,但在运行测试的时候没问题。“-”代表这个属性从没故障过。

3. SMART参数详解

一般情况下,用户只要观察当前值、最差值和临界值的关系,并注意状态提示信息即可大致了解硬盘的健康状况。下面简单介绍各参数的含义,以红色标出的项目是寿命关键项,蓝色为固态硬盘(SSD)特有的项目。

在基于闪存的固态硬盘中,存储单元分为两类:SLC(Single Layer Cell,单层单元)和MLC(Multi-Level Cell,多层单元)。SLC成本高、容量小、但读写速度快,可靠性高,擦写次数可高达100000次,比MLC高10倍。而MLC虽容量大、成本低,但其性能大幅落后于SLC。为了保证MLC的寿命,控制芯片还要有智能磨损平衡技术算法,使每个存储单元的写入次数可以平均分摊,以达到100万小时的平均无故障时间。因此固态硬盘有许多SMART参数是机械硬盘所没有的,如存储单元的擦写次数、备用块统计等等,这些新增项大都由厂家自定义,有些尚无详细的解释,有些解释也未必准确,此处也只是仅供参考。下面凡未注明厂商的固态硬盘特有的项均为SandForce主控芯片特有的,其它厂商各自单独注明。

01(001)底层数据读取错误率 Raw Read Error Rate

数据为0或任意值,当前值应远大于与临界值。

底层数据读取错误率是磁头从磁盘表面读取数据时出现的错误,对某些硬盘来说,大于0的数据表明磁盘表面或者读写磁头发生问题,如介质损伤、磁头污染、磁头共振等等。不过对希捷硬盘来说,许多硬盘的这一项会有很大的数据量,这不代表有任何问题,主要是看当前值下降的程度。

在固态硬盘中,此项的数据值包含了可校正的错误与不可校正的RAISE错误(UECC+URAISE)。

注:RAISE(Redundant Array of Independent Silicon Elements)意为独立硅元素冗余阵列,是固态硬盘特有的一种冗余恢复技术,保证内部有类似RAID阵列的数据安全性。

02(002)磁盘读写通量性能 Throughput Performance

此参数表示硬盘的读写通量性能,数据值越大越好。当前值如果偏低或趋近临界值,表示硬盘存在严重的问题,但现在的硬盘通常显示数据值为0或根本不显示此项,一般在进行了人工脱机SMART测试后才会有数据量。

03(003)主轴起旋时间 Spin Up Time

主轴起旋时间就是主轴电机从启动至达到额定转速所用的时间,数据值直接显示时间,单位为毫秒或者秒,因此数据值越小越好。不过对于正常硬盘来说,这一项仅仅是一个参考值,硬盘每次的启动时间都不相同,某次启动的稍慢些也不表示就有问题。

硬盘的主轴电机从启动至达到额定转速大致需要4秒~15秒左右,过长的启动时间说明电机驱动电路或者轴承机构有问题。旦这一参数的数据值在某些型号的硬盘上总是为0,这就要看当前值和最差值来判断了。

对于固态硬盘来说,所有的数据都是保存在半导体集成电路中,没有主轴电机,所以这项没有意义,数据固定为0,当前值固定为100。

04(004)启停计数 Start/Stop Count

这一参数的数据是累计值,表示硬盘主轴电机启动/停止的次数,新硬盘通常只有几次,以后会逐渐增加。系统的某些功能如空闲时关闭硬盘等会使硬盘启动/停止的次数大为增加,在排除定时功能的影响下,过高的启动/停止次数(远大于通电次数0C)暗示硬盘电机及其驱动电路可能有问题。

这个参数的当前值是依据某种公式计算的结果,例如对希捷某硬盘来说临界值为20,当前值是通过公式“100-(启停计数/1024)”计算得出的。若新硬盘的启停计数为0,当前值为100-(0/1024)=100,随着启停次数的增加,该值不断下降,当启停次数达到81920次时,当前值为100-(81920/1024)=20,已达到临界值,表示从启停次数来看,该硬盘已达设计寿命,当然这只是个寿命参考值,并不具有确定的指标性。

这一项对于固态硬盘同样没有意义,数据固定为0,当前值固定为100。

05(005)重映射扇区计数 Reallocated Sectors Count/ 退役块计数 Retired Block Count

数据应为0,当前值应远大于临界值。

当硬盘的某扇区持续出现读/写/校验错误时,硬盘固件程序会将这个扇区的物理地址加入缺陷表(G-list),将该地址重新定向到预先保留的备用扇区并将其中的数据一并转移,这就称为重映射。执行重映射操作后的硬盘在Windows常规检测中是无法发现不良扇区的,因其地址已被指向备用扇区,这等于屏蔽了不良扇区。

这项参数的数据值直接表示已经被重映射扇区的数量,当前值则随着数据值的增加而持续下降。当发现此项的数据值不为零时,要密切注意其发展趋势,若能长期保持稳定,则硬盘还可以正常运行;若数据值不断上升,说明不良扇区不断增加,硬盘已处于不稳定状态,应当考虑更换了。如果当前值接近或已到达临界值(此时的数据值并不一定很大,因为不同硬盘保留的备用扇区数并不相同),表示缺陷表已满或备用扇区已用尽,已经失去了重映射功能,再出现不良扇区就会显现出来并直接导致数据丢失。

这一项不仅是硬盘的寿命关键参数,而且重映射扇区的数量也直接影响硬盘的性能,例如某些硬盘会出现数据量很大,但当前值下降不明显的情况,这种硬盘尽管还可正常运行,但也不宜继续使用。因为备用扇区都是位于磁盘尾部(靠近盘片轴心处),大量的使用备用扇区会使寻道时间增加,硬盘性能明显下降。

这个参数在机械硬盘上是非常敏感的,而对于固态硬盘来说同样具有重要意义。闪存的寿命是正态分布的,例如说MLC能写入一万次以上,实际上说的是写入一万次之前不会发生“批量损坏”,但某些单元可能写入几十次就损坏了。换言之,机械硬盘的盘片不会因读写而损坏,出现不良扇区大多与工艺质量相关,而闪存的读写次数则是有限的,因而损坏是正常的。所以固态硬盘在制造时也保留了一定的空间,当某个存储单元出现问题后即把损坏的部分隔离,用好的部分来顶替。这一替换方法和机械硬盘的扇区重映射是一个道理,只不过机械硬盘正常时极少有重映射操作,而对于固态硬盘是经常性的。

在固态硬盘中这一项的数据会随着使用而不断增长,只要增长的速度保持稳定就可以。通常情况下,数据值=100-(100×被替换块/必需块总数),因此也可以估算出硬盘的剩余寿命。

Intel固态硬盘型号的第十二个字母表示了两种规格,该字母为1表示第一代的50纳米技术的SSD,为2表示第二代的34纳米技术的SSD,如SSDSA2M160G2GN就表示是34nm的SSD。所以参数的查看也有两种情况:

50nm的SSD(一代)要看当前值。这个值初始是100,当出现替换块的时候这个值并不会立即变化,一直到已替换四个块时这个值变为1,之后每增加四个块当前值就+1。也就是100对应0~3个块,1对应4~7个块,2对应8~11个块……

34nm的SSD(二代)直接查看数据值,数据值直接表示有多少个被替换的块。

06(006)读取通道余量 Read Channel Margin

这一项功能不明,现在的硬盘也不显示这一项。

07(007)寻道错误率 Seek Error Rate

数据应为0,当前值应远大于与临界值。

这一项表示磁头寻道时的错误率,有众多因素可导致寻道错误率上升,如磁头组件的机械系统、伺服电路有局部问题,盘片表面介质不良,硬盘温度过高等等。

通常此项的数据应为0,但对希捷硬盘来说,即使是新硬盘,这一项也可能有很大的数据量,这不代表有任何问题,还是要看当前值是否下降。

08(008)寻道性能 Seek Time Performance

此项表示硬盘寻道操作的平均性能(寻道速度),通常与前一项(寻道错误率)相关联。当前值持续下降标志着磁头组件、寻道电机或伺服电路出现问题,但现在许多硬盘并不显示这一项。

09(009)通电时间累计 Power-On Time Count (POH)

这个参数的含义一目了然,表示硬盘通电的时间,数据值直接累计了设备通电的时长,新硬盘当然应该接近0,但不同硬盘的计数单位有所不同,有以小时计数的,也有以分、秒甚至30秒为单位的,这由磁盘制造商来定义。

这一参数的临界值通常为0,当前值随着硬盘通电时间增加会逐渐下降,接近临界值表明硬盘已接近预计的设计寿命,当然这并不表明硬盘将出现故障或立即报废。参考磁盘制造商给出的该型号硬盘的MTBF(平均无故障时间)值,可以大致估计剩余寿命或故障概率。

对于固态硬盘,要注意“设备优先电源管理功能(device initiated power management,DIPM)”会影响这个统计:如果启用了DIPM,持续通电计数里就不包括睡眠时间;如果关闭了DIPM功能,那么活动、空闲和睡眠三种状态的时间都会被统计在内。

0A(010)主轴起旋重试次数 Spin up Retry Count

数据应为0,当前值应大于临界值。

主轴起旋重试次数的数据值就是主轴电机尝试重新启动的计数,即主轴电机启动后在规定的时间里未能成功达到额定转速而尝试再次启动的次数。数据量的增加表示电机驱动电路或是机械子系统出现问题,整机供电不足也会导致这一问题。

0B(011)磁头校准重试计数 Calibration Retry Count

数据应为0,当前值应远大于与临界值。

硬盘在温度发生变化时,机械部件(特别是盘片)会因热胀冷缩出现形变,因此需要执行磁头校准操作消除误差,有的硬盘还内置了磁头定时校准功能。这一项记录了需要再次校准(通常因上次校准失败)的次数。

这一项的数据量增加,表示电机驱动电路或是机械子系统出现问题,但有些型号的新硬盘也有一定的数据量,并不表示有问题,还要看当前值和最差值。

0C(012)通电周期计数 Power Cycle Count

通电周期计数的数据值表示了硬盘通电/断电的次数,即电源开关次数的累计,新硬盘通常只有几次。

这一项与启停计数(04)是有区别的,一般来说,硬盘通电/断电意味着计算机的开机与关机,所以经历一次开关机数据才会加1;而启停计数(04)表示硬盘主轴电机的启动/停止(硬盘在运行时可能多次启停,如系统进入休眠或被设置为空闲多少时间而关闭)。所以大多情况下这个通电/断电的次数会小于启停计数(04)的次数。

通常,硬盘设计的通电次数都很高,如至少5000次,因此这一计数只是寿命参考值,本身不具指标性。

0D(013)软件读取错误率 Soft Read Error Rate

软件读取错误率也称为可校正的读取误码率,就是报告给操作系统的未经校正的读取错误。数据值越低越好,过高则可能暗示盘片磁介质有问题。

AA(170)坏块增长计数 Grown Failing Block Count(Micron 镁光)

读写失败的块增长的总数。

AB(171)编程失败块计数 Program Fail Block Count

Flash编程失败块的数量。

AC(172)擦写失败块计数 Erase Fail Block Count

擦写失败块的数量。

AD(173)磨损平衡操作次数(平均擦写次数) / Wear Leveling Count(Micron 镁光)

所有好块的平均擦写次数。

Flash芯片有写入次数限制,当使用FAT文件系统时,需要频繁地更新文件分配表。如果闪存的某些区域读写过于频繁,就会比其它区域磨损的更快,这将明显缩短整个硬盘的寿命(即便其它区域的擦写次数还远小于最大限制)。所以,如果让整个区域具有均匀的写入量,就可明显延长芯片寿命,这称为磨损均衡措施。

AE(174)意外失电计数 Unexpected Power Loss Count

硬盘自启用后发生意外断电事件的次数。

B1(177)磨损范围对比值 Wear Range Delta

磨损最重的块与磨损最轻的块的磨损百分比之差。

B4(180)未用的备用块计数 Unused Reserved Block Count Total(惠普)

固态硬盘会保留一些容量来准备替换损坏的存储单元,所以可用的预留空间数非常重要。这个参数的当前值表示的是尚未使用的预留的存储单元数量。

B5(181)编程失败计数 Program Fail Count

用4个字节显示已编程失败的次数,与(AB)参数相似。

B5(181)非4KB对齐访问数 Non-4k Aligned Access(Micron 镁光)

B6(182)擦写失败计数 Erase Fail Count

用4个字节显示硬盘自启用后块擦写失败的次数,与(AC)参数相似。

B7(183)串口降速错误计数 SATA Downshift Error Count

这一项表示了SATA接口速率错误下降的次数。通常硬盘与主板之间的兼容问题会导致SATA传输级别降级运行。

B8(184)I/O错误检测与校正 I/O Error Detection and Correction(IOEDC)

“I/O错误检测与校正”是惠普公司专有的SMART IV技术的一部分,与其他制造商的I/O错误检测和校正架构一样,它记录了数据通过驱动器内部高速缓存RAM传输到主机时的奇偶校验错误数量。

B8(184)点到点错误检测计数 End to End Error Detection Count

Intel第二代的34nm固态硬盘有点到点错误检测计数这一项。固态硬盘里有一个LBA(logical block addressing,逻辑块地址)记录,这一项显示了SSD内部逻辑块地址与真实物理地址间映射的出错次数。

B8(184)原始坏块数 Init Bad Block Count(Indilinx芯片)

硬盘出厂时已有的坏块数量。

B9(185)磁头稳定性 Head Stability(西部数据)

意义不明。

BA(186)感应运算振动检测 nduced Op-Vibration Detection(西部数据)

意义不明。

BB(187)无法校正的错误 Reported Uncorrectable Errors(希捷)

报告给操作系统的无法通过硬件ECC校正的错误。如果数据值不为零,就应该备份硬盘上的数据了。

报告给操作系统的在所有存取命令中出现的无法校正的RAISE(URAISE)错误。

BC(188)命令超时 Command Timeout

由于硬盘超时导致操作终止的次数。通常数据值应为0,如果远大于零,最有可能出现的是电源供电问题或者数据线氧化致使接触不良,也可能是硬盘出现严重问题。

BD(189)高飞写入 High Fly Writes

磁头飞行高度监视装置可以提高读写的可靠性,这一装置时刻监测磁头的飞行高度是否在正常范围来保证可靠的写入数据。如果磁头的飞行高度出现偏差,写入操作就会停止,然后尝试重新写入或者换一个位置写入。这种持续的监测过程提高了写入数据的可靠性,同时也降低了读取错误率。这一项的数据值就统计了写入时磁头飞行高度出现偏差的次数。

BD(189)出厂坏块计数 Factory Bad Block Count(Micron 镁光芯片)

BE(190)气流温度 Airflow Temperature

这一项表示的是硬盘内部盘片表面的气流温度。在希捷公司的某些硬盘中,当前值=(100-当前温度),因此气流温度越高,当前值就越低,最差值则是当前值曾经到达过的最低点,临界值由制造商定义的最高允许温度来确定,而数据值不具实际意义。许多硬盘也没有这一项参数。

BF(191)冲击错误率 G-sense error rate

这一项的数据值记录了硬盘受到机械冲击导致出错的频度。

C0(192)断电返回计数 Power-Off Retract Count

当计算机关机或意外断电时,硬盘的磁头都要返回停靠区,不能停留在盘片的数据区里。正常关机时电源会给硬盘一个通知,即Standby Immediate,就是说主机要求将缓存数据写入硬盘,然后就准备关机断电了(休眠、待机也是如此);意外断电则表示硬盘在未收到关机通知时就失电,此时磁头会自动复位,迅速离开盘片。

这个参数的数据值累计了磁头返回的次数。但要注意这个参数对某些硬盘来说仅记录意外断电时磁头的返回动作;而某些硬盘记录了所有(包括休眠、待机,但不包括关机时)的磁头返回动作;还有些硬盘这一项没有记录。因此这一参数的数据值在某些硬盘上持续为0或稍大于0,但在另外的硬盘上则会大于通电周期计数(0C)或启停计数(04)的数据。在一些新型节能硬盘中,这一参数的数据量还与硬盘的节能设计相关,可能会远大于通电周期计数(0C)或启停计数(04)的数据,但又远小于磁头加载/卸载计数(C1)的数据量。

对于固态硬盘来说,虽然没有磁头的加载/卸载操作,但这一项的数据量仍然代表了不安全关机,即发生意外断电的次数。

C1(193)磁头加载/卸载计数 Load/Unload Cycle Count

对于过去的硬盘来说,盘片停止旋转时磁头臂停靠于盘片中心轴处的停泊区,磁头与盘片接触,只有当盘片旋转到一定转速时,磁头才开始漂浮于盘片之上并开始向外侧移动至数据区。这使得磁头在硬盘启停时都与盘片发生摩擦,虽然盘片的停泊区不存储数据,但无疑启停一个循环,就使磁头经历两次磨损。所以对以前的硬盘来说,磁头起降(加载/卸载)次数是一项重要的寿命关键参数。

而在现代硬盘中,平时磁头臂是停靠于盘片之外的一个专门设计的停靠架上,远离盘片。只有当盘片旋转达到额定转速后,磁头臂才开始向内(盘片轴心)转动使磁头移至盘片区域(加载),磁头臂向外转动返回至停靠架即卸载。这样就彻底杜绝了硬盘启停时磁头与盘片接触的现象,西部数据公司将其称为“斜坡加载技术”。由于磁头在加载/卸载过程中始终不与盘片接触,不存在磁头的磨损,使得这一参数的重要性已经大大下降。

这个参数的数据值就是磁头执行加载/卸载操作的累计次数。从原理上讲,这个加载/卸载次数应当与硬盘的启停次数相当,但对于笔记本内置硬盘以及台式机新型节能硬盘来说,这一项的数据量会很大。这是因为磁头臂组件设计有一个固定的返回力矩,保证在意外断电时磁头能靠弹簧力自动离开盘片半径范围,迅速返回停靠架。所以要让硬盘运行时磁头保持在盘片的半径之内,就要使磁头臂驱动电机(寻道电机)持续通以电流。而让磁头臂在硬盘空闲几分钟后就立即执行卸载动作,返回到停靠架上,既有利于节能,又降低了硬盘受外力冲击导致磁头与盘片接触的概率。虽然再次加载会增加一点寻道时间,但毕竟弊大于利,所以在这类硬盘中磁头的加载/卸载次数会远远大于通电周期计数(0C)或启停计数(04)的数据量。不过这种加载/卸载方式已经没有了磁头与盘片的接触,所以设计值也已大大增加,通常笔记本内置硬盘的磁头加载/卸载额定值在30~60万次,而台式机新型节能硬盘的磁头加载/卸载设计值可达一百万次。

C2(194)温度 Temperature

温度的数据值直接表示了硬盘内部的当前温度。硬盘运行时最好不要超过45℃,温度过高虽不会导致数据丢失,但引起的机械变形会导致寻道与读写错误率上升,降低硬盘性能。硬盘的最高允许运行温度可查看硬盘厂商给出的数据,一般不会超过60℃。

不同厂家对温度参数的当前值、最差值和临界值有不同的表示方法:希捷公司某些硬盘的当前值就是实际温度(摄氏)值,最差值则是曾经达到过的最高温度,临界值不具意义;而西部数据公司一些硬盘的最差值是温度上升到某值后的时间函数,每次升温后的持续时间都将导致最差值逐渐下降,当前值则与当前温度成反比,即当前温度越高,当前值越低,随实际温度波动。

C3(195)硬件ECC校正 Hardware ECC Recovered

ECC(Error Correcting Code)的意思是“错误检查和纠正”,这个技术能够容许错误,并可以将错误更正,使读写操作得以持续进行,不致因错误而中断。这一项的数据值记录了磁头在盘片上读写时通过ECC技术校正错误的次数,不过许多硬盘有其制造商特定的数据结构,因此数据量的大小并不能直接说明问题。

C3(195)实时无法校正错误计数 On the fly ECC Uncorrectable Error Count

这一参数记录了无法校正(UECC)的错误数量。

C3(195)编程错误块计数 Program Failure block Count(Indilinx芯片)

C4(196)重映射事件计数 Reallocetion Events Count

数据应为0,当前值应远大于临界值。

这个参数的数据值记录了将重映射扇区的数据转移到备用扇区的尝试次数,是重映射操作的累计值,成功的转移和不成功的转移都会被计数。因此这一参数与重映射扇区计数(05)相似,都是反映硬盘已经存在不良扇区。

C4(196)擦除错误块计数 Erase Failure block Count(Indilinx芯片)

在固态硬盘中,这一参数记录了被重映射的块编程失败的数量。

C5(197)当前待映射扇区计数 Current Pending Sector Count

数据应为0,当前值应远大于临界值。

这个参数的数据表示了“不稳定的”扇区数,即等待被映射的扇区(也称“被挂起的扇区”)数量。如果不稳定的扇区随后被读写成功,该扇区就不再列入等待范围,数据值就会下降。

仅仅读取时出错的扇区并不会导致重映射,只是被列入“等待”,也许以后读取就没有问题,所以只有在写入失败时才会发生重映射。下次对该扇区写入时如果继续出错,就会产生一次重映射操作,此时重映射扇区计数(05)与重映射事件计数(C4)的数据值增加,此参数的数据值下降。

C5(197)读取错误块计数(不可修复错误)Read Failure block Count(Indilinx芯片)

C6(198)脱机无法校正的扇区计数 Offline Uncorrectable Sector Count

数据应为0,当前值应远大于临界值。

这个参数的数据累计了读写扇区时发生的无法校正的错误总数。数据值上升表明盘片表面介质或机械子系统出现问题,有些扇区肯定已经不能读取,如果有文件正在使用这些扇区,操作系统会返回读盘错误的信息。下一次写操作时会对该扇区执行重映射。

C6(198)总读取页数 Total Count of Read Sectors(Indilinx芯片)

C7(199)Ultra ATA访问校验错误率 Ultra ATA CRC Error Rate

这个参数的数据值累计了通过接口循环冗余校验(Interface Cyclic Redundancy Check,ICRC)发现的数据线传输错误的次数。如果数据值不为0且持续增长,表示硬盘控制器→数据线→硬盘接口出现错误,劣质的数据线、接口接触不良都可能导致此现象。由于这一项的数据值不会复零,所以某些新硬盘也会出现一定的数据量,只要更换数据线后数据值不再继续增长,即表示问题已得到解决。

C7(199)总写入页数 Total Count of Write Sectors(Indilinx芯片)

C8(200)写入错误率 Write Error Rate / 多区域错误率 Multi-Zone Error Rate(西部数据)

数据应为0,当前值应远大于临界值。

这个参数的数据累计了向扇区写入数据时出现错误的总数。有的新硬盘也会有一定的数据量,若数据值持续快速升高(当前值偏低),表示盘片、磁头组件可能有问题。

C8(200)总读取指令数 Total Count of Read Command(Indilinx芯片)

C9(201)脱道错误率 Off Track Error Rate / 逻辑读取错误率 Soft Read Error Rate

数据值累积了读取时脱轨的错误数量,如果数据值不为0,最好备份硬盘上的资料。

C9(201)TA Counter Detected(意义不明)

C9(201)写入指令总数 Total Count of Write Command(Indilinx芯片)

CA(202)数据地址标记错误 Data Address Mark errors

此项的数据值越低越好(或者由制造商定义)。

CA(202)TA Counter Increased(意义不明)

CA(202)剩余寿命 Percentage Of The Rated Lifetime Used(Micron 镁光芯片)

当前值从100开始下降至0,表示所有块的擦写余量统计。计算方法是以MLC擦写次数除以50,SLC擦写次数除以1000,结果取整数,将其与100的差值作为当前值(MLC预计擦写次数为5000,SLC预计擦写次数为100000)。

CA(202)闪存总错误bit数 Total Count of error bits from flash(Indilinx芯片)

CB(203)软件ECC错误数 Run Out Cancel

错误检查和纠正(ECC)出错的频度。

CB(203)校正bit错误的总读取页数 Total Count of Read Sectors with correct bits error(Indilinx芯片)

CC(204)软件ECC校正 Soft ECC Correction

通过软件ECC纠正错误的计数。

CC(204)坏块满标志 Bad Block Full Flag(Indilinx芯片)

CD(205)热骚动错误率 Thermal Asperity Rate (TAR)

由超温导致的错误。数据值应为0。

CD(205)最大可编程/擦除次数 Max P/E Count(Indilinx芯片)

CE(206)磁头飞行高度 Flying Height

磁头距离盘片表面的垂直距离。高度过低则增加了磁头与盘片接触导致损坏的可能性;高度偏高则增大了读写错误率。不过准确地说,硬盘中并没有任何装置可以直接测出磁头的飞行高度,制造商也只是根据磁头读取的信号强度来推算磁头飞行高度。

CE(206)底层数据写入出错率 Write Error Rate

CE(206)最小擦写次数 Erase Count Min(Indilinx芯片)

CF(207)主轴过电流 Spin High Current

数据值记录了主轴电机运行时出现浪涌电流的次数,数据量的增加意味着轴承或电机可能有问题。

CF(207)最大擦写次数 Erase Count Max(Indilinx芯片)

D0(208)主轴电机重启次数 Spin Buzz

数据值记录了主轴电机反复尝试启动的次数,这通常是由于电源供电不足引起的。

D0(208)平均擦写次数Erase Count Average(Indilinx芯片)

D1(209)脱机寻道性能 Offline Seek Performance

这一项表示驱动器在脱机状态下的寻道性能,通常用于工厂内部测试。

D1(209)剩余寿命百分比 Remaining Life %(Indilinx芯片)

D2(210)斜坡加载值 Ramp Load Value

这一项仅见于几年前迈拓制造的部分硬盘。通常数据值为0,意义不明。

D2(210)坏块管理错误日志 BBM Error Log(Indilinx芯片)

D3(211)写入时振动 Vibration During Write

写入数据时受到受到外部振动的记录。

D3(211)SATA主机接口CRC写入错误计数 SATA Error Count CRC (Write)(Indilinx芯片)

D4(212)写入时冲击 Shock During Write

写入数据时受到受到外部机械冲击的记录。

D4(212)SATA主机接口读取错误计数 SATA Error Count Count CRC (Read)(Indilinx芯片)

DC(220)盘片偏移量 Disk Shift

硬盘中的盘片相对主轴的偏移量(通常是受外力冲击或温度变化所致),单位未知,数据值越小越好。

DD(221)冲击错误率 G-sense error rate

与(BF)相同,数据值记录了硬盘受到外部机械冲击或振动导致出错的频度。

DE(222)磁头寻道时间累计 Loaded Hours

磁头臂组件运行的小时数,即寻道电机运行时间累计。

DF(223)磁头加载/卸载重试计数 Load/Unload Retry Count

这一项与(C1)项类似,数据值累积了磁头尝试重新加载/卸载的次数。

E0(224)磁头阻力 Load Friction

磁头工作时受到的机械部件的阻力。

E1(225)主机写入数据量 Host Writes

由于闪存的擦写次数是有限的,所以这项是固态硬盘特有的统计。Intel的SSD是每当向硬盘写入了65536个扇区,这一项的数据就+1。如果用HDTune等软件查看SMART时可以自己计算,Intel SSD Toolbox已经为你算好了,直接就显示了曾向SSD中写入过的数据量。

E2(226)磁头加载时间累计 Load ‘In’-time

磁头组件运行时间的累积数,即磁头臂不在停靠区的时间,与(DE)项相似。

E3(227)扭矩放大计数 Torque Amplification Count

主轴电机试图提高扭矩来补偿盘片转速变化的次数。当主轴轴承存在问题时,主轴电机会尝试增加驱动力使盘片稳定旋转。这个参数的当前值下降,说明硬盘的机械子系统出现了严重的问题。

E4(228)断电返回计数 Power-Off Retract Cycle

数据值累计了磁头因设备意外断电而自动返回的次数,与(C0)项相似。

E6(230)GMR磁头振幅 GMR Head Amplitude

磁头“抖动”,即正向/反向往复运动的距离。

E7(231)温度 Temperature

温度的数据值直接表示了硬盘内部的当前温度,与(C2)项相同。

E7(231)剩余寿命 SSD Life Left

剩余寿命是基于P/E周期与可用的备用块作出的预测。新硬盘为100;10表示PE周期已到设计值,但尚有足够的保留块;0表示保留块不足,硬盘将处于只读方式以便备份数据。

E8(232)寿命余量 Endurance Remaining

寿命余量是指硬盘已擦写次数与设计最大可擦写次数的百分比,与(CA)项相似。

E8(232)预留空间剩余量 Available Reserved Space(Intel芯片)

对于Intel的SSD来说,前边05项提到会保留一些容量来准备替换损坏的存储单元,所以可用的预留空间数非常重要。当保留的空间用尽,再出现损坏的单元就将出现数据丢失,这个SSD的寿命就结束了。所以仅看05项意义并不大,这一项才最重要。这项参数可以看当前值,新的SSD里所有的预留空间都在,所以是100。随着预留空间的消耗,当前值将不断下降,减小到接近临界值(一般是10)时,就说明只剩下10%的预留空间了,SSD的寿命将要结束。这个与(B4)项相似。

E9(233)通电时间累计 Power-On Hours

对于普通硬盘来说,这一项与(09)相同。

E9(233)介质磨耗指数 Media Wareout Indicator(Intel芯片)

由于固态硬盘的擦写次数是有限的,当到达一定次数的时候,就会出现大量的单元同时损坏,这时候预留空间也顶不住了,所以这项参数实际上表示的是硬盘设计寿命。Intel的SSD要看当前值,随着NAND的平均擦写次数从0增长到最大的设计值,这一参数的当前值从开始的100逐渐下降至1为止。这表示SSD的设计寿命已经终结。当然到达设计寿命也不一定意味着SSD就立即报废,这与闪存芯片的品质有着很大的关系。

注:Total Erase Count全擦写计数是指固态硬盘中所有块的擦写次数的总和,不同规格的NAND芯片以及不同容量的SSD,其最大全擦写次数均有所不同。

F0(240)磁头飞行时间 Head Flying Hours / 传输错误率 Transfer Error Rate(富士通)

磁头位于工作位置的时间。

富士通硬盘表示在数据传输时连接被重置的次数。

F1(241)LBA写入总数 Total LBAs Written

LBA写入数的累计。

F1(241)写入剩余寿命 Lifetime Writes from Host

自硬盘启用后主机向硬盘写入的数据总量,以4个字节表示,每写入64GB字节作为一个单位。

F2(242)LBA读取总数 Total LBAs Read

LBA读取数的累计。某些SMART读取工具会显示负的数据值,是因为采用了48位LBA,而不是32位LBA。

F2(242)读取剩余寿命 Lifetime Reads from Host

自硬盘启用后主机从硬盘读取的数据总量,以4个字节表示,每读取64GB字节作为一个单位。

FA(250)读取错误重试率 Read Error Retry Rate

从磁盘上读取时出错的次数。

FE(254)自由坠落保护 Free Fall Protection

现在有些笔记本硬盘具有自由坠落保护功能,当硬盘内置的加速度探测装置检测到硬盘位移时,会立即停止读写操作,将磁头臂复位。这个措施防止了磁头与盘片之间发生摩擦撞击,提高了硬盘的抗震性能。这个参数的数据里记录了这一保护装置动作的次数。

7.4 nohup命令

nohup 英文全称 no hang up(不挂起),用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行。

1. 基本使用

语法格式

nohup Command [ Arg … ] [ & ]

参数说明:

Command:要执行的命令。

Arg:一些参数,可以指定输出文件。

&:让命令在后台执行,终端退出后命令仍旧执行。

实例

nohup /root/test.sh &

查看运行的后台进程

  1. jobs -l

  2. ps -ef

  3. 如果某个进程起不来,可能是某个端口被占用,查看端口进程:

lsof -i:8090

netstat -ap|grep 8090
  1. 终止后台运行的进程

kill -9 8090

2. 输出文件

以下命令在后台执行test.sh 脚本,并重定向输入到 mylog.log 文件:

nohup test.sh > mylog.log 2>&1 &

操作系统中有三个常用的流: 0 - 标准输入流 stdin  1 - 标准输出流 stdout  2 - 标准错误流 stderr

一般当我们用 > console.txt,实际是 1>console.txt的省略用法;< console.txt ,实际是 0 < console.txt的省略用法。

2>&1 & 解释:

  1. &的命令行,即使terminal(终端)关闭,或者电脑死机程序依然运行(前提是你把程序递交到服务器上);

  2. 2>&1的意思是把标准错误2重定向到标准输出中1,而标准输出又导入文件output里面,所以结果是标准错误和标准输出都导入文件output里面了。至于为什么需要将标准错误重定向到标准输出的原因,那就归结为标准错误没有缓冲区,而stdout有。这就会导致 >output 2>output 文件output被两次打开,而stdout和stderr将会竞争覆盖,这肯定不是我门想要的。这就是为什么有人会写成: nohup ./command.sh >output 2>output出错的原因了

3. 不输出日志文件

/dev/null(参见dd命令章节)文件的作用,这是一个无底洞,任何东西都可以定向到这里,但是却无法打开。

所以一般很大的stdou和stderr当你不关心的时候可以利用stdout和stderr定向到这里

>./command.sh >/dev/null 2>&1

4. 日志按天输出

输出日志在当前目录:

nohup command.sh >> nohup`date +%Y-%m-%d`.out 2>&1 &

指定输出到当前目录log文件夹中

nohup command.sh >> ./log/nohup`date +%Y-%m-%d`.out 2>&1 &

7.5 crontab定时任务

Linux crontab是用来定期执行程序的命令。

crond 命令每分钟会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。

注意:新创建的 cron 任务,不会马上执行,至少要过 2 分钟后才可以,可以重启 cron 来马上执行。

1. 基本使用

linux 任务调度的工作主要分为以下两类:

  • 系统执行的工作:系统周期性所要执行的工作,如备份系统数据、清理缓存

  • 个人执行的工作:某个用户定期要做的工作,例如每隔10分钟检查邮件服务器是否有新信,这些工作可由每个用户自行设置

语法

crontab [ -u user ] file

crontab [ -u user ] { -l | -r | -e }

说明:

crontab 是用来让使用者在固定时间或固定间隔执行程序之用,换句话说,也就是类似使用者的时程表。

-u user 是指设定指定 user 的时程表,这个前提是你必须要有其权限(比如说是 root)才能够指定他人的时程表。如果不使用 -u user 的话,就是表示设定自己的时程表。

参数说明

  • -e : 执行文字编辑器来设定时程表,内定的文字编辑器是 VI,如果你想用别的文字编辑器,则请先设定 VISUAL 环境变数来指定使用那个文字编辑器(比如说 setenv VISUAL joe)

  • -r : 删除目前的时程表

  • -l : 列出目前的时程表

时间格式如下:

f1 f2 f3 f4 f5 program
  • 其中 f1 是表示分钟,f2 表示小时,f3 表示一个月份中的第几日,f4 表示月份,f5 表示一个星期中的第几天。program 表示要执行的程序。

  • 当 f1 为 * 时表示每分钟都要执行 program,f2 为 * 时表示每小时都要执行程序,其馀类推

  • 当 f1 为 a-b 时表示从第 a 分钟到第 b 分钟这段时间内要执行,f2 为 a-b 时表示从第 a 到第 b 小时都要执行,其馀类推

  • 当 f1 为 /n 时表示每 n 分钟个时间间隔执行一次,f2 为/n 表示每 n 小时个时间间隔执行一次,其馀类推

  • 当 f1 为 a, b, c,… 时表示第 a, b, c,… 分钟要执行,f2 为 a, b, c,… 时表示第 a, b, c…个小时要执行,其馀类推

*    *    *    *    *
-    -    -    -    -
|    |    |    |    |
|    |    |    |    +----- 星期中星期几 (0 - 7) (星期天 为0)
|    |    |    +---------- 月份 (1 - 12)
|    |    +--------------- 一个月中的第几天 (1 - 31)
|    +-------------------- 小时 (0 - 23)
+------------------------- 分钟 (0 - 59)

使用者也可以将所有的设定先存放在文件中,用 crontab file 的方式来设定执行时间。

实例

每一分钟执行一次 /bin/ls:

* * * * * /bin/ls

在 12 月内, 每天的早上 6 点到 12 点,每隔 3 个小时 0 分钟执行一次 /usr/bin/backup:

0 6-12/3 * 12 * /usr/bin/backup

周一到周五每天下午 5:00 寄一封信给 alex@domain.name

0 17 * * 1-5 mail -s "hi" alex@domain.name < /tmp/maildata

每月每天的午夜 0 点 20 分, 2 点 20 分, 4 点 20 分….执行 echo “haha”:

20 0-23/2 * * * echo "haha"

下面再看看几个具体的例子:

0 */2 * * * /sbin/service httpd restart  意思是每两个小时重启一次apache

50 7 * * * /sbin/service sshd start  意思是每天7:50开启ssh服务

50 22 * * * /sbin/service sshd stop  意思是每天22:50关闭ssh服务

0 0 1,15 * * fsck /home  每月1号和15号检查/home 磁盘

1 * * * * /home/bruce/backup  每小时的第一分执行 /home/bruce/backup这个文件

00 03 * * 1-5 find /home "*.xxx" -mtime +4 -exec rm {} \;  每周一至周五3点钟,在目录/home中,查找文件名为*.xxx的文件,并删除4天前的文件。

30 6 */10 * * ls  意思是每月的1、11、21、31日是的6:30执行一次ls命令

注意:当程序在你所指定的时间执行后,系统会发一封邮件给当前的用户,显示该程序执行的内容,若是你不希望收到这样的邮件,请在每一行空一格之后加上 > /dev/null 2>&1 即可,如:

20 03 * * * . /etc/profile;/bin/sh test.sh > /dev/null 2>&1

2. 常用命令

crontab服务操作说明:

/sbin/service crond start //启动服务

/sbin/service crond stop //关闭服务

/sbin/service crond restart //重启服务

/sbin/service crond reload //重新载入配置

查看crontab服务状态:

service crond status

手动启动crontab服务:

service crond status

查看crontab服务是否已设置为开机启动,执行命令:

方法一: 界面启动   ntsysv

方法二: 加入开机自动启动:  chkconfig –level 35 crond on

查看定时任务列表

crontab -l

编辑定时任务

crontab –e
或
vim /var/spool/cron/root

crontab -r 删除定时任务

从/var/spool/cron目录中删除用户的crontab文件

如果不指定用户,则默认删除当前用户的crontab文件

crontab –i 在删除用户的crontab 文件时给确认提示

备份crontab文件

crontab -l > $HOME/mycron

恢复丢失的crontab文件

如果不小心误删了crontab文件,假设你在自己的$HOME目录下还有一个备份,那么可以将其拷贝到/var/spool/cron/,其中是用户名。如果由于权限问题无法完成拷贝,可以用:crontab 其中,是你在$HOME目录中副本的文件名。

有些crontab的变体有些怪异,所以在使用crontab命令时要格外小心。如果遗漏了任何选项,crontab可能会打开一个空文件,或者看起来像是个空文件。这时敲delete键退出,不要按,否则你将丢失crontab文件。

3. 脚本无法执行问题

如果我们使用 crontab 来定时执行脚本,无法执行,但是如果直接通过命令(如:./test.sh)又可以正常执行,这主要是因为无法读取环境变量的原因。

解决方法:

  1. 所有命令需要写成绝对路径形式,如: /usr/local/bin/docker

  2. 在 shell 脚本开头使用以下代码:

#!/bin/sh

. /etc/profile
. ~/.bash_profile
  1. /etc/crontab 中添加环境变量,在可执行命令之前添加命令 . /etc/profile;/bin/sh例如:

20 03 * * * . /etc/profile;/bin/sh test.sh

7.6 vim常用命令

1. vim的几种模式

正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空
插入模式(按i进入) 左下角显示--INSERT--
可视模式(按v进入) 左下角显示--VISUAL--
替换模式(按r或R开始) 左下角显示 --REPLACE--
命令行模式(按:或者/或者?开始)
ex模式 没用过,有兴趣的同学可以自行了解

2. 启动vim

# 直接启动vim
vim

# 打开vim并创建名为filename的文件
vim filename

# 打开单个文件
vim file

# 同时打开多个文件
vim file1 file2 file3 ...

# 在vim窗口中打开一个新文件
:open file

# 在新窗口中打开文件
:split file

# 切换到下一个文件
:bn

# 切换到上一个文件
:bp

# 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来
:args

# 打开远程文件,比如ftp或者share folder
:e ftp://192.168.10.76/abc.txt
:e \qadrive est.txt

# 以只读形式打开文件,但是仍然可以使用 :wq! 写入
vim -R file

# 强制性关闭修改功能,无法使用 :wq! 写入
vim -M file

3. 导航命令

% 括号匹配

4. 插入命令

i 在当前位置生前插入

I 在当前行首插入

a 在当前位置后插入

A 在当前行尾插入

o 在当前行之后插入一行

O 在当前行之前插入一行

5. 查找命令

/text 查找text,按n健查找下一个,按N健查找前一个。

?text 查找text,反向查找,按n健查找下一个,按N健查找前一个。

vim中有一些特殊字符在查找时需要转义  .*[]^%/?~$

:set ignorecase 忽略大小写的查找

:set noignorecase 不忽略大小写的查找

查找很长的词,如果一个词很长,键入麻烦,可以将光标移动到该词上,按*或#键即可以该单词进行搜索,相当于/搜索。而#命令相当于?搜索。

:set hlsearch 高亮搜索结果,所有结果都高亮显示,而不是只显示一个匹配。

:set nohlsearch 关闭高亮搜索显示

:nohlsearch 关闭当前的高亮显示,如果再次搜索或者按下n或N键,则会再次高亮。

:set incsearch 逐步搜索模式,对当前键入的字符进行搜索而不必等待键入完成。

:set wrapscan 重新搜索,在搜索到文件头或尾时,返回继续搜索,默认开启。

快速查找,不需要手打字符即可查找

*        向后(下)寻找游标所在处的单词
#        向前(上)寻找游标所在处的单词


以上两种查找,n,N 的继续查找命令依然可以适用

精准查找:匹配单词查找

如果文本中有 hellohelloworldhellopython

那我使用 /hello ,这三个词都会匹配到。

有没有办法实现精准查找呢?可以使用

/hello\>

精准查找:匹配行首、行末

# hello位于行首
/^hello

# world位于行末
/world$

6. 替换命令

ra 将当前字符替换为a,当期字符即光标所在字符。

s/old/new/ 用old替换new,替换当前行的第一个匹配

s/old/new/g 用old替换new,替换当前行的所有匹配

%s/old/new/ 用old替换new,替换所有行的第一个匹配

%s/old/new/g 用old替换new,替换整个文件的所有匹配

:10,20 s/^/    /g 在第10行知第20行每行前面加四个空格,用于缩进。

ddp 交换光标所在行和其下紧邻的一行。

7. 移动命令

h 左移一个字符
l 右移一个字符,这个命令很少用,一般用w代替。
k 上移一个字符
j 下移一个字符

以上四个命令可以配合数字使用,比如20j就是向下移动20行,5h就是向左移动5个字符,在Vim中,很多命令都可以配合数字使用,比如删除10个字符10x,在当前位置后插入3个!,3a!,这里的Esc是必须的,否则命令不生效。

w 向前移动一个单词(光标停在单词首部),如果已到行尾,则转至下一行行首。此命令快,可以代替l命令。

b 向后移动一个单词 2b 向后移动2个单词

e,同w,只不过是光标停在单词尾部

ge,同b,光标停在单词尾部。

^ 移动到本行第一个非空白字符上。

0(数字0)移动到本行第一个字符上,

 移动到本行第一个字符。同0健。

$ 移动到行尾 3$ 移动到下面3行的行尾

gg 移动到文件头。 = [[

G(shift + g) 移动到文件尾。 = ]]

f(find)命令也可以用于移动,fx将找到光标后第一个为x的字符,3fd将找到第三个为d的字符。

F 同f,反向查找。

跳到指定行,冒号+行号,回车,比如跳到240行就是 :240回车。另一个方法是行号+G,比如230G跳到230行。

Ctrl + e 向下滚动一行

Ctrl + y 向上滚动一行

Ctrl + d 向下滚动半屏

Ctrl + u 向上滚动半屏

Ctrl + f 向下滚动一屏

Ctrl + b 向上滚动一屏

8. 撤销重做

u 撤销(Undo)

U 撤销对整行的操作

Ctrl + r 重做(Redo),即撤销的撤销。

9. 删除命令

以字符为单位删除

x   删除当前字符
3x  删除当前字符3次

X   删除当前字符的前一个字符。
3X  删除当前光标向前三个字符

dl  删除当前字符, dl=x
dh  删除前一个字符,X=dh

D   删除当前字符至行尾。D=d$
d$  删除当前字符至行尾
d^  删除当前字符之前至行首

以单词为单位删除

dw  删除当前字符到单词尾
daw 删除当前字符所在单词

以行为单位删除

dd  删除当前行
dj  删除下一行
dk  删除上一行

dgg  删除当前行至文档首部
d1G  删除当前行至文档首部
dG   删除当前行至文档尾部

kdgg  删除当前行之前所有行(不包括当前行)
jdG   删除当前行之后所有行(不包括当前行)



10d     删除当前行开始的10行。
:1,10d  删除1-10行
:11,$d  删除11行及以后所有的行
:1,$d   删除所有行
J     删除两行之间的空行,实际上是合并两行。

10. 复制黏贴

普通模式中使用y复制

yy   复制游标所在的整行(3yy表示复制3行)

y^   复制至行首,或y0。不含光标所在处字符。
y$   复制至行尾。含光标所在处字符。

yw   复制一个单词。
y2w  复制两个单词。

yG   复制至文本末。
y1G  复制至文本开头。

普通模式中使用p粘贴

p(小写):代表粘贴至光标后(下边,右边)
P(大写):代表粘贴至光标前(上边,左边)

11. 剪切命令

dd    其实就是剪切命令,剪切当前行

ddp   剪切当前行并粘贴,可实现当前行和下一行调换位置


正常模式下按v(逐字)或V(逐行)进入可视模式,然后用jklh命令移动即可选择某些行或字符,再按d即可剪切

ndd 剪切当前行之后的n行。利用p命令可以对剪切的内容进行粘贴

:1,10d 将1-10行剪切。利用p命令可将剪切后的内容进行粘贴。

:1, 10 m 20 将第1-10行移动到第20行之后。

12. 退出命令

:wq 保存并退出

ZZ 保存并退出

:q! 强制退出并忽略所有更改

:e! 放弃所有修改,并打开原来文件。

13. 窗口命令

:split或new 打开一个新窗口,光标停在顶层的窗口上

:split file或:new file 用新窗口打开文件

split打开的窗口都是横向的,使用vsplit可以纵向打开窗口。

Ctrl+ww 移动到下一个窗口

Ctrl+wj 移动到下方的窗口

Ctrl+wk 移动到上方的窗口

:close 最后一个窗口不能使用此命令,可以防止意外退出vim。

:q 如果是最后一个被关闭的窗口,那么将退出vim。

ZZ 保存并退出。

关闭所有窗口,只保留当前窗口

:only

14. 排版功能

缩进

:set shiftwidth?   查看缩进值
:set shiftwidth=4  设置缩进值为4

# 缩进相关 最好写到配置文件中  ~/.vimrc
:set tabstop=4
:set softtabstop=4
:set shiftwidth=4
:set expandtab

>>   向右缩进
<<   取消缩进

如何你要对代码进行缩进,还可以用 == 对当前行缩进,如果要对多行对待缩进,则使用 n==,这种方式要求你所编辑的文件的扩展名是被vim所识别的,比如.py文件。

排版

:ce   居中
:le   靠左
:ri   靠右

15. 注释命令

多行注释

进入命令行模式,按ctrl + v进入 visual block模式,然后按j, 或者k选中多行,把需要注释的行标记起来

按大写字母I,再插入注释符,例如//

按esc键就会全部注释了

取消多行注释

进入命令行模式,按ctrl + v进入 visual block模式,按字母l横向选中列的个数,例如 // 需要选中2列

按字母j,或者k选中注释符号

按d键就可全部取消注释

复杂注释

:3,5 s/^/#/g 注释第3-5行
:3,5 s/^#//g 解除3-5行的注释


:1,$ s/^/#/g 注释整个文档
:1,$ s/^#//g 取消注释整个文档


:%s/^/#/g 注释整个文档,此法更快
:%s/^#//g 取消注释整个文档

16. 调整视野

"zz":命令会把当前行置为屏幕正中央,
"zt":命令会把当前行置于屏幕顶端
"zb":则把当前行置于屏幕底端.

Ctrl + e 向下滚动一行
Ctrl + y 向上滚动一行

Ctrl + d 向下滚动半屏
Ctrl + u 向上滚动半屏

Ctrl + f 向下滚动一屏
Ctrl + b 向上滚动一屏


【跳到指定行】:两种方法

可以先把行号打开
:set nu  打开行号

:20    跳到第20行
20G    跳到第20行

17. 区域选择

要进行区域选择,要先进入可视模式

v   以字符为单位,上下左右选择
V   以行为单位,上下选择

选择后可进行操作
d   剪切/删除
y   复制

Ctrl+v   如果当前是V(大写)模式,就变成v(小写)
         如果当前是v(小写)模式,就变成普通模式。
         如果当前是普通模式,就进入v(小写)模式

利用这个,可以进行多行缩进。

ggVG   选择全文

18. 窗口控制

新建窗口

# 打开两个文件分属两个窗口
vim -o 1.txt 2.txt


# 假设现在已经打开了1.txt

:sp 2.txt   开启一个横向的窗口,编辑2.txt
:vsp 2.txt  开启一个竖向的窗口,编辑2.txt

:split        将当前窗口再复制一个窗口出来,内容同步,游标可以不同
:split 2.txt  在新窗口打开2.txt的横向窗口

# 需要注意:内容同步,但是游标位置是独立的

Ctrl-w s    将当前窗口分成水平窗口
Ctrl-w v    将当前窗口分成竖直窗口

Ctrl-w q    等同:q 结束分割出来的视窗。
Ctrl-w q!   等同:q! 结束分割出来的视窗。
Ctrl-w o    打开一个视窗并且隐藏之前的所有视窗

窗口切换

# 特别说明:Ctrl w <字母> 不需要同时按

Ctrl-w h    切换到左边窗口
Ctrl-w l    切换到右边窗口

Ctrl-w j    切换到下边窗口
Ctrl-w k    切换到上边窗口


# 特别说明:全屏模式下
:n    切换下一个窗口
:N    切换上一个窗口
:bp   切换上一个窗口

# 特别说明:非全屏模式

:bn    切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变
:bN    切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变

窗口移动

# 特别说明:Ctrl w <字母> 不需要同时按

Ctrl-w J   将当前视窗移至最下面
Ctrl-w K   将当前视窗移最上面

Ctrl-w H   将当前视窗移至最左边
Ctrl-w L   将当前视窗移至最右边

Ctrl-ww    按顺序切换窗口

调整尺寸

Ctrl-w +   增加窗口高度

Ctrl-w -   减少窗口高度

退出窗口

:close    关闭当前窗口
:close!   强制关闭当前窗口

:q       退出,不保存
:q!      强制退出,不保存

:x       保存退出
:wq      保存退出
:wq!     强制保存退出

:w <[路径/]文件名>        另存为
:savesa <[路径/]文件名>   另存为

ZZ 保存并退出。

:only    关闭所有窗口,只保留当前窗口(前提:其他窗口内容有改变的话都要先保存)
:only!   关闭所有窗口,只保留当前窗口

:qall 放弃所有操作并退出
:wall 保存所有,
:wqall 保存所有并退出。

19. 文档加密

vim -x file_name

然后输入密码:
确认密码:

如果不修改内容也要保存。:wq,不然密码设定不会生效。

20. 执行命令

# 重复前一次命令
.

# 执行shell命令
:!command

# 比如列出当前目录下文件
:!ls

# 执行脚本
:!perl -c script.pl 检查perl脚本语法,可以不用退出vim,非常方便。
:!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。

:suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。

# 执行完当前命令后,自动回到vim环境,但不知为何文件内容变成空了
:silent !command

21. 帮助命令

在Unix/Linux系统上
$ vimtutor

# 普通模式下
键盘输入vim或F1

# 命令行模式下

:help     显示整个帮助
:help xxx 显示xxx的帮助,比如 :help i, :help CTRL-[(即Ctrl+[的帮助)。
:help 'number' Vim选项的帮助用单引号括起


在Windows系统上
:help tutor

22. 配置命令

显示当前设定

:set或者:se显示所有修改过的配置
:set all 显示所有的设定值
:set option? 显示option的设定值
:set nooption 取消当期设定值
:ver   显示vim的所有信息(包括版本和参数等)

# 需要注意:全屏模式下
:args   查看当前打开的文件列表,当前正在编辑的文件会用[]括起来

更改设定

:set nu   显示行号

set autoindent(ai)   设置自动缩进
set autowrite(aw)    设置自动存档,默认未打开
set backup(bk) 设置自动备份,默认未打开

set background=dark或light,设置背景风格

set cindent(cin) 设置C语言风格缩进

:set ts=4   设置tab键转换为4个空格

:set ff=unix   # 修改文件dos文件为unix

:set shiftwidth?   查看缩进值
:set shiftwidth=4  设置缩进值为4

:set ignorecase  忽略大小写的查找
:set noignorecase  不忽略大小写的查找

:set paste  # insert模式下,粘贴格式不会乱掉

:set ruler?  查看是否设置了ruler,在.vimrc中,使用set命令设制的选项都可以通过这个命令查看

:scriptnames  查看vim脚本文件的位置,比如.vimrc文件,语法文件及plugin等。

:set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。


:syntax        列出已经定义的语法项
:syntax clear  清除已定义的语法规则

:syntax case match    大小写敏感,int和Int将视为不同的语法元素
:syntax case ignore   大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案

第八章:网络基础

8.1 常见的HTTP状态码

1. 基本的响应代码

  • 200(“OK”)

    一切正常。实体主体中的文档(若存在的话)是某资源的表示。

  • 400(“Bad Request”)

    客户端方面的问题。实体主题中的文档(若存在的话)是一个错误消息。希望客户端能够理解此错误消息,并改正问题。

  • 500(“Internal Server Error”)

    服务期方面的问题。实体主体中的文档(如果存在的话)是一个错误消息。该错误消息通常无济于事,因为客户端无法修复服务器方面的问题。

  • 301(“Moved Permanently”)

    当客户端触发的动作引起了资源URI的变化时发送此响应代码。另外,当客户端向一个资源的旧URI发送请求时,也发送此响应代码。

  • 404(“Not Found”) 和410(“Gone”)

    当客户端所请求的URI不对应于任何资源时,发送此响应代码。404用于服务器端不知道客户端要请求哪个资源的情况;410用于服务器端知道客户端所请求的资源曾经存在,但现在已经不存在了的情况。

  • 409(“Conflict”)

    当客户端试图执行一个”会导致一个或多个资源处于不一致状态“的操作时,发送此响应代码。

SOAP Web服务只使用响应代码200(“OK”)和500(“Internal Server Error”)。无论是你发给SOAP服务器的数据有问题,还是服务器在处理数据的过程中出现问题,或者SOAP服务器出现内部问题,SOAP服务器均发送500(“Internal Server Error”)。客户端只有查看SOAP文档主体(body)(其中包含错误的描述)才能获知错误原因。客户端无法仅靠读取响应的前三个字节得知请求成功与否。

2. 状态码系列

1XX:通知

1XX系列响应代码仅在与HTTP服务器沟通时使用。

  • 100(“Continue”)

    重要程度:中等,但(写操作时)很少用。

这是对HTTP LBYL(look-before-you-leap)请求的一个可能的响应。该响应代码表明:客户端应重新发送初始请求,并在请求中附上第一次请求时未提供的(可能很大或者包含敏感信息的)表示。客户端这次发送的请求不会被拒绝。对LBYL请求的另一个可能的响应是417(“Expectation Failed”)。

请求报头:要做一个LBYL请求,客户端必须把Expect请求报头设为字符串“100-continue”。除此以外,客户端还需要设置其他一些报头,服务器将根据这些报头决定是响应100还是417。

  • 101(“Switching Protocols”)

    重要程度:非常低。

当客户端通过在请求里使用Upgrade报头,以通知服务器它想改用除HTTP协议之外的其他协议时,客户端将获得此响应代码。101响应代码表示“行,我现在改用另一个协议了”。通常HTTP客户端会在收到服务器发来的101响应后关闭与服务器的TCP连接。101响应代码意味着,该客户端不再是一个HTTP客户端,而将成为另一种客户端。

尽管可以通过Upgrade报头从HTTP切换到HTTPS,或者从HTTP1.1切换到某个未来的版本,但实际使用Upgrade报头的情况比较少。Upgrade报头也可用于HTTP切换到一个完全不同的协议(如IRC)上,但那需要在Web服务器切换为一个IRC服务器的同时,Web客户端切换为一个IRC的客户端,因为服务器将立刻在同一个TCP连接上开始使用新的协议。

请求报头:客户端把Upgrade报头设置为一组希望使用的协议。

响应报头:如果服务器同意切换协议,它就返回一个Upgrade报头,说明它将切换到那个协议,并附上一个空白行。服务器不用关闭TCP链接,而是直接在该TCP连接上开始使用新的协议。

2XX: 成功

2XX系列响应代码表明操作成功了。

  • 200(“OK”)

    重要程度:非常高。

一般来说,这是客户端希望看到的响应代码。它表示服务器成功执行了客户端所请求的动作,并且在2XX系列里没有其他更适合的响应代码了。

实体主体:对于GET请求,服务器应返回客户端所请求资源的一个表示。对于其他请求,服务器应返回当前所选资源的一个表示,或者刚刚执行的动作的一个描述。

  • 201(“Created”)

    重要程度:高。

当服务器依照客户端的请求创建了一个新资源时,发送此响应代码。

响应报头:Location报头应包含指向新创建资源的规范URI。

实体主体:应该给出新创建资源的描述与链接。若已经在Location报头里给出了新资源的URI,那么可以用新资源的一个表示作为实体主体。

  • 202(“Accepted”)

    重要程度:中等。

客户端的请求无法或将不被实时处理。请求稍后会被处理。请求看上去是合法的,但在实际处理它时有出现问题的可能。

若一个请求触发了一个异步操作,或者一个需要现实世界参与的动作,或者一个需要很长时间才能完成且没必要让Web客户端一直等待的动作时,这个相应代码是一个合适的选择。

响应报头:应该把未处理完的请求暴露为一个资源,以便客户端稍后查询其状态。Location报头可以包含指向该资源的URI。

实体主体:若无法让客户端稍后查询请求的状态,那么至少应该提供一个关于何时能处理该请求的估计。

  • 203(“Non-Authoritative Information”)

    重要程度:非常低。

这个响应代码跟200一样,只不过服务器想让客户端知道,有些响应报头并非来自该服务器–他们可能是从客户端先前发送的一个请求里复制的,或者从第三方得到的。

响应报头:客户端应明白某些报头可能是不准确的,某些响应报头可能不是服务器自己生成的,所以服务器也不知道其含义。

  • 204(“No Content”)

    重要程度:高。

若服务器拒绝对PUT、POST或者DELETE请求返回任何状态信息或表示,那么通常采用此响应代码。服务器也可以对GET请求返回此响应代码,这表明“客户端请求的资源存在,但其表示是空的”。注意与304(“Not Modified”)的区别。204常常用在Ajax应用里。服务器通过这个响应代码告诉客户端:客户端的输入已被接受,但客户端不应该改变任何UI元素。

实体主体:不允许。

  • 205(“Reset Content”)

    重要程度:低。

它与204类似,但与204不同的是,它表明客户端应重置数据源的视图或数据结构。假如你在浏览器里提交一个HTML表单,并得到响应代码204,那么表单里的各个字段值不变,可以继续修改它们;但假如得到的响应代码205,那么表单里的各个字段将被重置为它们的初始值。从数据录入方面讲:204适合对单条记录做一系列编辑,而205适于连续输入一组记录。

  • 206(“Partial Content”)

    重要程度:对于支持部分GET(partial GET)的服务而言“非常高”,其他情况下“低”。

它跟200类似,但它用于对部分GET请求(即使用Range请求报头的GET请求)的响应。部分GET请求常用于大型二进制文件的断点续传。

请求报头:客户端为Range请求报头设置一个值。

响应报头:需要提供Date报头。ETag报头与Content-Location报头的值应该跟正常GET请求相同。

若实体主体是单个字节范围(byte range),那么HTTP响应里必须包含一个Content-Range报头,以说明本响应返回的是表示的哪个部分,若实体主体是一个多部分实体(multipart entity)(即该实体主体由多个字节范围构成),那么每一个部分都要有自己的Content-Range报头。

实体主体:不是整个表示,而是一个或者多个字节范围。

3XX 重定向

3XX系列响应代码表明:客户端需要做些额外工作才能得到所需要的资源。它们通常用于GET请求。他们通常告诉客户端需要向另一个URI发送GET请求,才能得到所需的表示。那个URI就包含在Location响应报头里。

  • 300(“Multiple Choices”)

    重要程度:低。

若被请求的资源在服务器端存在多个表示,而服务器不知道客户端想要的是哪一个表示时,发送这个响应代码。或者当客户端没有使用Accept-*报头来指定一个表示,或者客户端所请求的表示不存在时,也发送这个响应代码。在这种情况下,一种选择是,服务器返回一个首选表示,并把响应代码设置为200,不过它也可以返回一个包含该资源各个表示的URI列表,并把响应代码设为300。

响应报头:如果服务器有首选表示,那么它可以在Location响应报头中给出这个首选表示的URI。跟其他3XX响应代码一样,客户端可以自动跟随Location中的URI。

实体主体:一个包含该资源各个表示的URI的列表。可以在表示中提供一些信息,以便用户作出选择。

  • 301(“Moved Permanently”)

    重要程度:中等。

服务器知道客户端试图访问的是哪个资源,但它不喜欢客户端用当前URI来请求该资源。它希望客户端记住另一个URI,并在今后的请求中使用那个新的URI。你可以通过这个响应代码来防止由于URI变更而导致老URI失效。

响应报头:服务器应当把规范URI放在Location响应报头里。

实体主体:服务器可以发送一个包含新URI的信息,不过这不是必需的。

  • 302(“Found”)

    重要程度:应该了解,特别市编写客户端时。但我不推荐使用它。

这个响应代码市造成大多数重定向方面的混乱的最根本原因。它应该是像307那样被处理。实际上,在HTTP 1.0中,响应代码302的名称是”Moved Temporarily”,不幸的是,在实际生活中,绝大多数客户端拿它像303一样处理。它的不同之处在于当服务器为客户端的PUT,POST或者DELETE请求返回302响应代码时,客户端要怎么做。

为了消除这一混淆,在HTTP 1.1中,该响应代码被重命名为“Found”,并新加了一个响应代码307。这个响应代码目前仍在广泛使用,但它的含义市混淆的,所以我建议你的服务发送307或者303,而不要发送302.除非你知道正在与一个不能理解303或307的HTTP 1.0客户端交互。

响应报头:把客户端应重新请求的那个URI放在Location报头里。

实体主体:一个包含指向新URI的链接的超文本文档(就像301一样)。

  • 303(“See Other”)

    重要程度:高。

请求已经被处理,但服务器不是直接返回一个响应文档,而是返回一个响应文档的URI。该响应文档可能是一个静态的状态信息,也可能是一个更有趣的资源。对于后一种情况,303是一种令服务器可以“发送一个资源的表示,而不强迫客户端下载其所有数据”的方式。客户端可以向Location报头里的URI发送GET请求,但它不是必须这么做。

303响应代码是一种规范化资源URI的好办法。一个资源可以有多个URIs,但每个资源的规范URI只有一个,该资源的所有其他URIs都通过303指向该资源的规范URI,例如:303可以把一个对http://www.example.com/software/current.tar.gz的请求重定向到http://www.example.com/software/1.0.2.tar.gz。

响应报头:Location报头里包含资源的URI。

实体主体:一个包含指向新URI的链接的超文本文档。

  • 304(“Not Modified”)

    重要程度:高。

这个响应代码跟204(“No Content”)类似:响应实体主体都必须为空。但204用于没有主体数据的情况,而304用于有主体数据,但客户端已拥有该数据,没必要重复发送的情况。这个响应代码可用于条件HTTP请求(conditional HTTP request).如果客户端在发送GET请求时附上了一个值为Sunday的If-Modified-Since报头,而客户端所请求的表示在服务器端自星期日(Sunday)以来一直没有改变过,那么服务器可以返回一个304响应。服务器也可以返回一个200响应,但由于客户端已拥有该表示,因此重复发送该表示只会白白浪费宽带。

响应报头:需要提供Date报头。Etag与Content-Location报头的值,应该跟返回200响应时的一样。若Expires, Cache-Control及Vary报头的值自上次发送以来已经改变,那么就要提供这些报头。

实体主体:不允许。

  • 305(“Use Proxy”)

    重要程度:低。

这个响应代码用于告诉客户端它需要再发一次请求,但这次要通过一个HTTP代理发送,而不是直接发送给服务器。这个响应代码使用的不多,因为服务器很少在意客户端是否使用某一特定代理。这个代码主要用于基于代理的镜像站点。现在,镜像站点(如http://www.example.com.mysite.com/)包含跟原始站点(如 http://www.example.com/)一样的内容,但具有不同的URI,原始站点可以通过307把客户端重新定向到镜像站点上。假如有基于代理的镜像站点,那么你可以通过把 http://proxy.mysite.com/设为代理,使用跟原始URI(http://www.example.com/)一样的URI来访问镜像站点。这里,原始站点example.com可以通过305把客户端路由到一个地理上接近客户端的镜像代理。web浏览器一般不能正确处理这个响应代码,这是导致305响应代码用的不多的另一个原因。

响应报头:Location报头里包含代理的URI。

  • 306 未使用

    重要程度:无

306 响应代码没有在HTTP标准中定义过。

  • 307(“Temporary Redirect”)

    重要程度:高。

请求还没有被处理,因为所请求的资源不在本地:它在另一个URI处。客户端应该向那个URI重新发送请求。就GET请求来说,它只是请求得到一个表示,该响应代码跟303没有区别。当服务器希望把客户端重新定向到一个镜像站点时,可以用307来响应GET请求。但对于POST,PUT及DELETE请求,它们希望服务器执行一些操作,307和303有显著区别。对POST,PUT或者DELETE请求响应303表明:操作已经成功执行,但响应实体将不随本响应一起返回,若客户端想要获取响应实体主体,它需要向另一个URI发送GET请求。而307表明:服务器尚未执行操作,客户端需要向Location报头里的那个URI重新提交整个请求。

响应报头: 把客户端应重新请求的那个URI放在Location报头里。

实体主体:一个包含指向新URI的链接的超文本文档。

4XX:客户端错误

这些响应代码表明客户端出现错误。不是认证信息有问题,就是表示格式或HTTP库本身有问题。客户端需要自行改正。

  • 400(“Bad Request”)

    重要程度:高。

这是一个通用的客户端错误状态,当其他4XX响应代码不适用时,就采用400。此响应代码通常用于“服务器收到客户端通过PUT或者POST请求提交的表示,表示的格式正确,但服务器不懂它什么意思”的情况。

实体主体:可以包含一个错误的描述文档。

  • 401(“Unauthorized”)

    重要程度:高。

客户端试图对一个受保护的资源进行操作,却又没有提供正确的认证证书。客户端提供了错误的证书,或者根本没有提供证书。这里的证书(credential)可以是一个用户名/密码,也可以市一个API key,或者一个认证令牌。客户端常常通过向一个URI发送请求,并查看收到401响应,以获知应该发送哪种证书,以及证书的格式。如果服务器不想让未授权的用户获知某个资源的存在,那么它可以谎报一个404而不是401。这样做的缺点是:客户端需要事先知道服务器接受哪种认证–这将导致HTTP摘要认证无法工作。

响应报头:WWW-Authenticate报头描述服务器将接受哪种认证。

实体主体:一个错误的描述文档。假如最终用户可通过“在网站上注册”的方式得到证书,那么应提供一个指向该注册页面的链接。

  • 402(“Payment Required”)

    重要程度:无。

除了它的名字外,HTTP标准没有对该响应的其他方面作任何定义。因为目前还没有用于HTTP的微支付系统,所以它被留作将来使用。尽管如此,若存在一个用于HTTP的微支付系统,那么这些系统将首先出现在web服务领域。如果想按请求向用户收费,而且你与用户之间的关系允许这么做的话,那么或许用得上这个响应代码。

  • 403(“Forbidden”)

    重要程度:中等。

客户端请求的结构正确,但是服务器不想处理它。这跟证书不正确的情况不同–若证书不正确,应该发送响应代码401。该响应代码常用于一个资源只允许在特定时间段内访问,

或者允许特定IP地址的用户访问的情况。403暗示了所请求的资源确实存在。跟401一样,若服务器不想透露此信息,它可以谎报一个404。既然客户端请求的结构正确,那为什么还要把本响应代码放在4XX系列(客户端错误),而不是5XX系列(服务端错误)呢?因为服务器不是根据请求的结构,而是根据请求的其他方面(比如说发出请求的时间)作出的决定的。

实体主体:一个描述拒绝原因的文档(可选)。

  • 404(“Not Found”)

    重要程度:高。

这也许是最广为人知的HTTP响应代码了。404表明服务器无法把客户端请求的URI转换为一个资源。相比之下,410更有用一些。web服务可以通过404响应告诉客户端所请求的URI是空的,然后客户端就可以通过向该URI发送PUT请求来创建一个新资源了。但是404也有可能是用来掩饰403或者401.

  • 405(“Method Not Allowd”)

    重要程度:中等。

客户端试图使用一个本资源不支持的HTTP方法。例如:一个资源只支持GET方法,但是客户端使用PUT方法访问。

响应报头:Allow报头列出本资源支持哪些HTTP方法,例如:Allow:GET,POST

  • 406(“Not Acceptable”)

    重要程度:中等。

当客户端对表示有太多要求,以至于服务器无法提供满足要求的表示,服务器可以发送这个响应代码。例如:客户端通过Accept头指定媒体类型为application/json+hic,但是服务器只支持application/json。服务器的另一个选择是:忽略客户端挑剔的要求,返回首选表示,并把响应代码设为200。

实体主体:一个可选表示的链接列表。

  • 407(“Proxy Authentication Required”)

    重要程度:低。

只有HTTP代理会发送这个响应代码。它跟401类似,唯一区别在于:这里不是无权访问web服务,而是无权访问代理。跟401一样,可能是因为客户端没有提供证书,也可能是客户端提供的证书不正确或不充分。

请求报头:客户端通过使用Proxy-Authorization报头(而不是Authorization)把证书提供给代理。格式跟Authrization一样。

响应报头:代理通过Proxy-Authenticate报头(而不是WWW-Authenticate)告诉客户端它接受哪种认证。格式跟WWW-Authenticate一样。

  • 408(“Reqeust Timeout”)

    重要程度:低。

假如HTTP客户端与服务器建立链接后,却不发送任何请求(或从不发送表明请求结束的空白行),那么服务器最终应该发送一个408响应代码,并关闭此连接。

  • 409(“Conflict”)

    重要程度:高。

此响应代码表明:你请求的操作会导致服务器的资源处于一种不可能或不一致的状态。例如你试图修改某个用户的用户名,而修改后的用户名与其他存在的用户名冲突了。

响应报头:若冲突是因为某个其他资源的存在而引起的,那么应该在Location报头里给出那个资源的URI。

实体主体:一个描述冲突的文档,以便客户端可以解决冲突。

  • 410(“Gone”)

    重要程度:中等。

这个响应代码跟404类似,但它提供的有用信息更多一些。这个响应代码用于服务器知道被请求的URI过去曾指向一个资源,但该资源现在不存在了的情况。服务器不知道

该资源的新URI,服务器要是知道该URI的话,它就发送响应代码301.410和310一样,都有暗示客户端不应该再请求该URI的意思,不同之处在于:410只是指出该资源不存在,但没有给出该资源的新URI。RFC2616建议“为短期的推广服务,以及属于个人但不继续在服务端运行的资源”采用410.

  • 411(“Length Required”)

    重要程度:低到中等。

若HTTP请求包含表示,它应该把Content-Length请求报头的值设为该表示的长度(以字节为单位)。对客户端而言,有时这不太方便(例如,当表示是来自其他来源的字节流时)。

所以HTTP并不要求客户端在每个请求中都提供Content-Length报头。但HTTP服务器可以要求客户端必须设置该报头。服务器可以中断任何没有提供Content-Length报头的请求,并要求客户端重新提交包含Content-Length报头的请求。这个响应代码就是用于中断未提供Content-Lenght报头的请求的。假如客户端提供错误的长度,或发送超过长度的表示,服务器可以中断请求并关闭链接,并返回响应代码413。

  • 412(“Precondition Failed”)

    重要程度:中等。

客户端在请求报头里指定一些前提条件,并要求服务器只有在满足一定条件的情况下才能处理本请求。若服务器不满足这些条件,就返回此响应代码。If-Unmodified-Since是一个常见的前提条件。客户端可以通过PUT请求来修改一个资源,但它要求,仅在自客户端最后一次获取该资源后该资源未被别人修改过才能执行修改操作。若没有这一前提条件,客户端可能会无意识地覆盖别人做的修改,或者导致409的产生。

请求报头:若客户但设置了If-Match,If-None-Match或If-Unmodified-Since报头,那就有可能得到这个响应代码。If-None-Match稍微特别一些。若客户端在发送GET或HEAD请求时指定了If-None-Match,并且服务器不满足该前提条件的话,那么响应代码不是412而是304,这是实现条件HTTP GET的基础。若客户端在发送PUT,POST或DELETE请求时指定了If-None-Match,并且服务器不满足该前提条件的话,那么响应代码是412.另外,若客户端指定了If-Match或If-Unmodified-Since(无论采用什么HTTP方法),而服务器不满足该前提条件的话,响应代码也是412。

  • 413(“Request Entity Too Large”)

    重要程度:低到中等。

这个响应代码跟411类似,服务器可以用它来中断客户端的请求并关闭连接,而不需要等待请求完成。411用于客户端未指定长度的情况,而413用于客户端发送的表示太大,以至于服务器无法处理。客户端可以先做一个LBYL(look-before-you-leap)请求,以免请求被413中断。若LBYL请求获得响应代码为100,客户端再提交完整的表示。

响应报头:如果因为服务器方面临时遇到问题(比如资源不足),而不是因为客户端方面的问题而导致中断请求的话,服务器可以把Retry-After报头的值设为一个日期或一个间隔时间,以秒为单位,以便客户端可以过段时间重试。

  • 414(“Request-URI Too Long”)

    重要程度:低。

HTTP标准并没有对URI长度作出官方限制,但大部分现有的web服务器都对URI长度有一个上限,而web服务可能也一样。导致URI超长的最常见的原因是:表示数据明明是该放在实体主体里的,但客户端却把它放在了URI里。深度嵌套的数据结构也有可能引起URI过长。

  • 415(“Unsupported Media Type”)

    重要程度:中等。

当客户端在发送表示时采用了一种服务器无法理解的媒体类型,服务器发送此响应代码。比如说,服务器期望的是XML格式,而客户端发送的确实JSON格式。

如果客户端采用的媒体类型正确,但格式有问题,这时最好返回更通用的400。

  • 416(“Requestd Range Not Satisfiable”)

    重要程度:低。

当客户端所请求的字节范围超出表示的实际大小时,服务器发送此响应代码。例如:你请求一个表示的1-100字节,但该表示总共只用99字节大小。

请求报头:仅当原始请求里包含Range报头时,才有可能收到此响应代码。若原始请求提供的是If-Range报头,则不会收到此响应代码。

响应报头:服务器应当通过Content-Range报头告诉客户端表示的实际大小。

  • 417(“Expectation Failed”)

    重要程度:中等。

此响应代码跟100正好相反。当你用LBYL请求来考察服务器是否会接受你的表示时,如果服务器确认会接受你的表示,那么你将获得响应代码100,否则你将获得417。

5XX 服务端错误

这些响应代码表明服务器端出现错误。一般来说,这些代码意味着服务器处于不能执行客户端请求的状态,此时客户端应稍后重试。有时,服务器能够估计客户端应在多久之后重试。并把该信息放在Retry-After响应报头里。

5XX系列响应代码在数量上不如4XX系列多,这不是因为服务器错误的几率小,而是因为没有必要如此详细–对于服务器方面的问题,客户端是无能为力的。

  • 500(“Internal Server Error”)

    重要程度:高。

这是一个通用的服务器错误响应。对于大多数web框架,如果在执行请求处理代码时遇到了异常,它们就发送此响应代码。

  • 501(“Not Implemented”)

    重要程度:低。

客户端试图使用一个服务器不支持的HTTP特性。

最常见的例子是:客户端试图做一个采用了拓展HTTP方法的请求,而普通web服务器不支持此请求。它跟响应代码405比较相似,405表明客户端所用的方法是一个可识别的方法,但该资源不支持,而501表明服务器根本不能识别该方法。

  • 502(“Bad Gateway”)

    重要程度:低。

只有HTTP代理会发送这个响应代码。它表明代理方面出现问题,或者代理与上行服务器之间出现问题,而不是上行服务器本身有问题。若代理根本无法访问上行服务器,响应代码将是504。

  • 503(“Service Unavailable”)

    重要程度:中等到高。

此响应代码表明HTTP服务器正常,只是下层web服务服务不能正常工作。最可能的原因是资源不足:服务器突然收到太多请求,以至于无法全部处理。由于此问题多半由客户端反复发送请求造成,因此HTTP服务器可以选择拒绝接受客户端请求而不是接受它,并发送503响应代码。

响应报头:服务器可以通过Retry-After报头告知客户端何时可以重试。

  • 504(“Gateway Timeout”)

    重要程度:低。

跟502类似,只有HTTP代理会发送此响应代码。此响应代码表明代理无法连接上行服务器。

  • 505(“HTTP Version Not Supported”)

    重要程度: 非常低。

当服务器不支持客户端试图使用的HTTP版本时发送此响应代码。

实体主体:一个描述服务器支持哪些协议的文档。

8.2 HTTP Header

HTTP Header 大体分为RequestResponse两部分。

1. Requests

Accept:

  • 解释:指定客户端能够接收的内容类型

  • 示例:Accept: text/plain, text/html

Accept-Charset::

  • 解释:浏览器可以接受的字符编码集

  • 示例:Accept-Charset: iso-8859-5

Accept-Encoding:

  • 解释:指定浏览器可以支持的web服务器返回内容压缩编码类型

  • 示例:Accept-Encoding: compress, gzip

Accept-Language:

  • 解释:浏览器可接受的语言

  • 示例:Accept-Language: en,zh

Accept-Ranges:

  • 解释:可以请求网页实体的一个或者多个子范围字段

  • 示例:Accept-Ranges: bytes

Authorization:

  • 解释:HTTP授权的授权证书

  • 示例:Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Cache-Control:

  • 解释:指定请求和响应遵循的缓存机制

  • 示例:Cache-Control: no-cache

Connection:

  • 解释:表示是否需要持久连接。(HTTP 1.1默认进行持久连接)

  • 示例:Connection: close

Cookie:

  • 解释:HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器

  • 示例:Cookie: $Version=1; Skin=new;

Content-Length:

  • 解释:请求的内容长度

  • 示例:Content-Length: 348

Content-Type:

  • 解释:请求的与实体对应的MIME信息

  • 示例:Content-Type: application/x-www-form-urlencoded

Date:

  • 解释:请求发送的日期和时间

  • 示例:Date: Tue, 15 Nov 2010 08:12:31 GMT

Expect:

  • 解释:请求的特定的服务器行为

  • 示例:Expect: 100-continue

From:

  • 解释:发出请求的用户的Email

  • 示例:From: https://www.xxx.com/mailto:user@email.com

Host:

  • 解释:指定请求的服务器的域名和端口号

  • 示例:Host: http://www.xxx.com

If-Match:

  • 解释:只有请求内容与实体相匹配才有效

  • 示例:If-Match: “737060cd8c284d8af7ad3082f209582d”

If-Modified-Since:

  • 解释:如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码

  • 示例:If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT

If-None-Match:

  • 解释:如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变

  • 示例:If-None-Match: “737060cd8c284d8af7ad3082f209582d”

If-Range:

  • 解释:如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag

  • 示例:If-Range: “737060cd8c284d8af7ad3082f209582d”

If-Unmodified-Since:

  • 解释:只在实体在指定时间之后未被修改才请求成功

  • 示例:If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT

Max-Forwards:

  • 解释:限制信息通过代理和网关传送的时间

  • 示例:Max-Forwards: 10

Pragma:

  • 解释:用来包含实现特定的指令

  • 示例:Pragma: no-cache

Proxy-Authorization:

  • 解释:连接到代理的授权证书

  • 示例:Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Range:

  • 解释:只请求实体的一部分,指定范围

  • 示例:Range: bytes=500-999

Referer:

  • 解释:先前网页的地址,当前请求网页紧随其后,即来路

  • 示例:Referer: http://www.xxx.com/a.html

TE:

  • 解释:客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息

  • 示例:TE: trailers,deflate;q=0.5

Upgrade:

  • 解释:向服务器指定某种传输协议以便服务器进行转换(如果支持)

  • 示例:Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11

User-Agent:

  • 解释:User-Agent的内容包含发出请求的用户信息

  • 示例:User-Agent: Mozilla/5.0 (Linux; X11)

Via:

  • 解释:通知中间网关或代理服务器地址,通信协议

  • 示例:Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)

Warning:

  • 解释:关于消息实体的警告信息

  • 示例:Warn: 199 Miscellaneous warning

2. Responses

Accept-Ranges:

  • 解释:表明服务器是否支持指定范围请求及哪种类型的分段请求

  • 示例:Accept-Ranges: bytes

Age:

  • 解释:从原始服务器到代理缓存形成的估算时间(以秒计,非负)

  • 示例:Age: 12

Allow:

  • 解释:对某网络资源的有效的请求行为,不允许则返回405

  • 示例:Allow: GET, HEAD

Cache-Control:

  • 解释:告诉所有的缓存机制是否可以缓存及哪种类型

  • 示例:Cache-Control: no-cache

Content-Encoding:

  • 解释:web服务器支持的返回内容压缩编码类型

  • 示例:Content-Encoding: gzip

Content-Language:

  • 解释:响应体的语言

  • 示例:Content-Language: en,zh

Content-Length:

  • 解释:响应体的长度

  • 示例:Content-Length: 348

Content-Location:

  • 解释:请求资源可替代的备用的另一地址

  • 示例:Content-Location: /index.htm

Content-MD5:

  • 解释:返回资源的MD5校验值

  • 示例:Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==

Content-Range:

  • 解释:在整个返回体中本部分的字节位置

  • 示例:Content-Range: bytes 21010-47021/47022

Content-Type:

  • 解释:返回内容的MIME类型

  • 示例:Content-Type: text/html; charset=utf-8

Date:

  • 解释:原始服务器消息发出的时间

  • 示例:Date: Tue, 15 Nov 2010 08:12:31 GMT

ETag:

  • 解释:请求变量的实体标签的当前值

  • 示例:ETag: “737060cd8c284d8af7ad3082f209582d”

Expires:

  • 解释:响应过期的日期和时间

  • 示例:Expires: Thu, 01 Dec 2010 16:00:00 GMT

Last-Modified:

  • 解释:请求资源的最后修改时间

  • 示例:Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT

Location:

  • 解释:用来重定向接收方到非请求URL的位置来完成请求或标识新的资源

  • 示例:Location: http://www.zcmhi.com/archives/94.html

Pragma:

  • 解释:包括实现特定的指令,它可应用到响应链上的任何接收方

  • 示例:Pragma: no-cache

Proxy-Authenticate:

  • 解释:它指出认证方案和可应用到代理的该URL上的参数

  • 示例:Proxy-Authenticate: Basic

refresh:

  • 解释:应用于重定向或一个新的资源被创造,在5秒之后重定向

  • **示例:Refresh: 5; url=http://www.zcmhi.com/archives/94.html

Retry-After:

  • 解释:如果实体暂时不可取,通知客户端在指定时间之后再次尝试

  • 示例:Retry-After: 120

Server:

  • 解释:web服务器软件名称

  • 示例:Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)

Set-Cookie:

  • 解释:设置Http Cookie

  • 示例:Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1

Trailer:

  • 解释:指出头域在分块传输编码的尾部存在

  • 示例:Trailer: Max-Forwards

Transfer-Encoding:

  • 解释:文件传输编码

  • 示例:Transfer-Encoding:chunked

Vary:

  • 解释:告诉下游代理是使用缓存响应还是从原始服务器请求

  • 示例:Vary: *

Via:

  • 解释:告知代理客户端响应是通过哪里发送的

  • 示例:Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)

Warning:

  • 解释:警告实体可能存在的问题

  • 示例:Warning: 199 Miscellaneous warning

WWW-Authenticate:

  • 解释:表明客户端请求实体应该使用的授权方案

  • 示例:WWW-Authenticate: Basic

8.3 HTTP和HTTPS的区别

1. HTTP和HTTPS的基本概念

HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。

HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。

Http原理

  1. 客户端的浏览器首先要通过网络与服务器建立连接,该连接是通过TCP 来完成的,一般 TCP 连接的端口号是80。 建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号,后边是 MIME 信息包括请求修饰符、客户机信息和许可内容。

  2. 服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是 MIME 信息包括服务器信息、实体信息和可能的内容。

HTTPS是以安全为目标的HTTP通道,是HTTP的安全版。HTTPS的安全基础是SSL。SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。

SSL协议可分为两层:

  • SSL记录协议(SSL Record Protocol),它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。

  • SSL握手协议(SSL Handshake Protocol),它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。

HTTPS设计目标

  1. 数据保密性:保证数据内容在传输的过程中不会被第三方查看。就像快递员传递包裹一样,都进行了封装,别人无法获知里面装了什么 。

  2. 数据完整性:及时发现被第三方篡改的传输内容。就像快递员虽然不知道包裹里装了什么东西,但他有可能中途掉包,数据完整性就是指如果被掉包,我们能轻松发现并拒收 。

  3. 身份校验安全性:保证数据到达用户期望的目的地。就像我们邮寄包裹时,虽然是一个封装好的未掉包的包裹,但必须确定这个包裹不会送错地方,通过身份校验来确保送对了地方 。

HTTPS原理

  1. 客户端将它所支持的算法列表和一个用作产生密钥的随机数发送给服务器;

  2. 服务器从算法列表中选择一种加密算法,并将它和一份包含服务器公用密钥的证书发送给客户端;该证书还包含了用于认证目的的服务器标识,服务器同时还提供了一个用作产生密钥的随机数;

  3. 客户端对服务器的证书进行验证,并抽取服务器的公用密钥;然后,再产生一个称作 pre_master_secret 的随机密码串,并使用服务器的公用密钥对其进行加密(参考非对称加 / 解密),并将加密后的信息发送给服务器;

  4. 客户端与服务器端根据 pre_master_secret 以及客户端与服务器的随机数值独立计算出加密和MAC密钥(参考 DH密钥交换算法) ;

  5. 客户端将所有握手消息的 MAC 值发送给服务器;

  6. 服务器将所有握手消息的 MAC 值发送给客户端。

2. HTTP与HTTPS的区别

  1. HTTPS协议需要到CA(Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。

  2. HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议。

  3. HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

  4. HTTP的连接很简单,是无状态的。HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)

3. HTTPS相对于HTTP的改进

双向的身份认证

客户端和服务端在传输数据之前,会通过基于X.509证书对双方进行身份认证 。具体过程如下:

  1. 客户端发起 SSL 握手消息给服务端要求连接。

  2. 服务端将证书发送给客户端。

  3. 客户端检查服务端证书,确认是否由自己信任的证书签发机构签发。 如果不是,将是否继续通讯的决定权交给用户选择 ( 注意,这里将是一个安全缺陷 )。如果检查无误或者用户选择继续,则客户端认可服务端的身份。

  4. 服务端要求客户端发送证书,并检查是否通过验证。失败则关闭连接,认证成功则从客户端证书中获得客户端的公钥,一般为1024位或者 2048位。到此,服务器客户端双方的身份认证结束,双方确保身份都是真实可靠的。

数据传输的机密性

客户端和服务端在开始传输数据之前,会协商传输过程需要使用的加密算法。 客户端发送协商请求给服务端, 其中包含自己支持的非对成加密的密钥交换算法 ( 一般是RSA), 数据签名摘要算法 ( 一般是SHA或者MD5) , 加密传输数据的对称加密算法 ( 一般是DES),以及加密密钥的长度。 服务端接收到消息之后,选中安全性最高的算法,并将选中的算法发送给客户端,完成协商。客户端生成随机的字符串,通过协商好的非对称加密算法,使用服务端的公钥对该字符串进行加密,发送给服务端。 服务端接收到之后,使用自己的私钥解密得到该字符串。在随后的数据传输当中,使用这个字符串作为密钥进行对称加密。

防止重放攻击

SSL使用序列号来保护通讯方免受报文重放攻击。这个序列号被加密后作为数据包的负载。在整个SSL握手中,都有一个唯一的随机数来标记SSL握手。 这样防止了攻击者嗅探整个登录过程,获取到加密的登录数据之后,不对数据进行解密, 而直接重传登录数据包的攻击手法。

可以看到,鉴于电子商务等安全上的需求,Https对比Http协议,在安全方面已经取得了极大的增强。总结来说,Https的改进点在于创造性的使用了非对称加密算法,在不安全的网路上,安全的传输了用来进行非对称加密的密钥,综合利用了非对称加密的安全性和对称加密的快速性。

4. HTTPS的优点

  1. 使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器。

  2. HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、修改,确保数据的完整性。

  3. HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。

5. HTTPS的缺点(对比优点)

  1. HTTPS协议握手阶段比较费时,会使页面的加载时间延长近。

  2. HTTPS连接缓存不如HTTP高效,会增加数据开销,甚至已有的安全措施也会因此而受到影响。

  3. HTTPS协议的安全是有范围的,在黑客攻击、拒绝服务攻击和服务器劫持等方面几乎起不到什么作用。

  4. SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。

  5. 成本增加。部署 HTTPS后,因为 HTTPS协议的工作要增加额外的计算资源消耗,例如 SSL 协议加密算法和 SSL 交互次数将占用一定的计算资源和服务器成本。

  6. HTTPS协议的加密范围也比较有限。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。

6. HTTPS的连接过程

  1. 客户端的浏览器向服务器发送请求,并传送客户端SSL 协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息。

  2. 服务器向客户端传送SSL 协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。

  3. 客户端利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;如果合法性验证通过,将继续进行第四步。

  4. 用户端随机产生一个用于通讯的“对称密码”,然后用服务器的公钥(服务器的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的“预主密码”传给服务器。

  5. 如果服务器要求客户的身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器。

  6. 如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:客户的证书使用日期是否有效,为客户提供证书的CA 是否可靠,发行CA 的公钥能否正确解开客户证书的发行CA 的数字签名,检查客户的证书是否在证书废止列表(CRL)中。检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。

  7. 服务器和客户端用相同的主密码即“通话密码”,一个对称密钥用于SSL 协议的安全数据通讯的加解密通讯。同时在SSL 通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。

  8. 客户端向服务器端发出信息,指明后面的数据通讯将使用的步骤7中的主密码为对称密钥,同时通知服务器客户端的握手过程结束。

  9. 服务器向客户端发出信息,指明后面的数据通讯将使用的步骤7中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。

  10. SSL 的握手部分结束,SSL 安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。

7. SSL提供的服务

  1. 认证用户和服务器,确保数据发送到正确的客户机和服务器

  2. 加密数据以防止数据中途被窃取

  3. 维护数据的完整性,确保数据在传输过程中不被改变

8. SSL工作流程

服务器认证阶段

  1. 客户端向服务器发送一个开始信息“Hello”以便开始一个新的会话连接

  2. 服务器根据客户的信息确定是否需要生成新的主密钥,如需要则服务器在响应客户的“Hello”信息时将包含生成主密钥所需的信息

  3. 客户根据收到的服务器响应信息,产生一个主密钥,并用服务器的公开密钥加密后传给服务器

  4. 服务器回复该主密钥,并返回给客户一个用主密钥认证的信息,以此让客户认证服务器。

用户认证阶段:

在此之前,服务器已经通过了客户认证,这一阶段主要完成对客户的认证。经认证的服务器发送一个提问给客户,客户则返回(数字)签名后的提问和其公开密钥,从而向服务器提供认证。

SSL协议提供的安全通道有以下三个特性:

机密性:SSL协议使用密钥加密通信数据。

可靠性:服务器和客户都会被认证,客户的认证是可选的。

完整性:SSL协议会对传送的数据进行完整性检查。

服务器证书(server certificates)是SSL数字证书的一种形式,意指通过提交数字证书来证明您的身份或表明您有权访问在线服务。再者简单来说,通过使用服务器证书可为不同站点提供身份鉴定并保证该站点拥有高强度加密安全。是组成Web服务器的SSL安全功能的唯一的数字标识。通过相互信任的第三方组织获得,并为用户 提供验证您Web站点身份的手段。服务器证书包含详细的身份验证信息,如服务器内容附属的组织、颁发证书的组织以及称为公开密钥的唯一的身份验证文件。

第九章:踩坑记录

9.1 Linux环境下Jupyter中matplotlib中文乱码

解决方法:

  1. 下载SimHei字体(百度搜一个就可以)

  2. 找到matplotlib的文件位置

import matplotlib
matplotlib.matplotlib_fname()

查看到matplotlib的文件路径,我的路径是:

/apps/home/rd/anaconda3/lib/python3.7/site-packages/matplotlib/mpl-data/matplotlibrc

进入到mpl-data/fonts路径,把下载好的SimHei.ttf文件导入进去

  1. 返回到mpl-data目录,打开matplotlibrc文件,搜索定位到font.family 取消font.familyfont.seriffont.sans-serif的注释,并在font.seriffont.sans-serif后添加SimHei,保存

  2. 进入到~/.cache目录,删除该目录下的matplotlib文件夹

cd ~/.cache
rm -rf  matplotlib
  1. 重新打开jupyter,可以输入如下代码查看是否导入SimHei字体

from matplotlib import font_manager
a = sorted([f.name for f in font_manager.fontManager.ttflist])
for i in a:
    print(i)
  1. 在导入matplotlib包之后加一句

plt.rcParams['font.sans-serif']=['SimHei']

第十章:数据结构与算法

第十一章:数据可视化

11.1 Matploatlib

1. 什么是Matploatlib

Matploatlib是Python的一个2D绘图库,它可以与Numpy一起使用,提供了一种有效的MatLab开源替代方案。Matploatlib试图让简单的事情更简单,让无法实现的事情变得可以实现,只需要几行代码就可以生成绘图。

2. 常见的图形及意义

  • 折线图:以折现的上升或下降来表示统计数量的增减变化

特点:能够显示数据的变化趋势,反应失误的变化情况。

img1.png

img1.png

  • 散点图:用两组数据构成多个坐标点,考察坐标点的分布,判断两个变量之间是否存在某种关联或总结坐标点的分布模式

特点:判断变量之间是否存在数量变化趋势,展示离群点(分布规律)

img2.png

img2.png

  • 柱状图:排列在工作表的列或行中的数据可以绘制到柱状图中。

特点:绘制连离散的数据,能够一眼看出各个数据的大小,比较数据之间的差别。(统计/对比)

img3.png

img3.png

  • 直方图:由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据范围,纵轴表示分布情况。

特点:绘制连续性的数据展示一组或者多组数据的分布状况(统计)

img4.png

img4.png

  • 饼图:用于表示不同分类的占比情况,通过弧度大小来对比各种分类。

特点:分类数据的占比情况(占比)

img5.png

img5.png

3. Matploatlib安装

使用清华源安装

pip3 install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple

例子:

# 导入模块
import matplotlib.pyplot as plt
import numpy as np
# 在jupyter中执行的时候显示图片
%matplotlib inline
x = np.arange(1,11)
y =  2  * x +  5
plt.title("Matplotlib demo")
plt.xlabel("x axis caption")
plt.ylabel("y axis caption")
plt.plot(x,y)
plt.show()
img6.png

img6.png

4. 折线图

4.1 折线图的绘制

from matplotlib import pyplot as plt
import random
x = range(10) # x轴的位置
y = [random.randint(5, 20) for i in range(10)]
# 传入x和y, 通过plot画折线图
plt.plot(x,y)
plt.show()
img7.png

img7.png

4.2 折线图的颜色和形状设置

from matplotlib import pyplot as plt
import random
x = range(10) # x轴的位置
y = [random.randint(5, 20) for i in range(10)]
# 传入x和y, 通过plot画折线图
plt.plot(x, y, color='red',alpha=0.5,linestyle='--',linewidth=3)
plt.show()
'''

基础属性设置
color='red' : 折线的颜色
alpha=0.5 : 折线的透明度(0-1)
linestyle='--' : 折线的样式
linewidth=3 : 折线的宽度

线的样式
- 实线(solid)
-- 短线(dashed)
-. 短点相间线(dashdot)
: 虚点线(dotted)

'''
img8.png

img8.png

4.3 折点样式

from matplotlib import pyplot as plt
import random
x = range(10) # x轴的位置
y = [random.randint(5, 20) for i in range(10)]
# 传入x和y, 通过plot画折线图
plt.plot(x, y, marker='o')
plt.savefig('1.png')

'''
折点形状选择:
================ ===============================
character description
================ ===============================
``'-'`` solid line style
``'--'`` dashed line style
``'-.'`` dash-dot line style
``':'`` dotted line style
``'.'`` point marker
``','`` pixel marker
``'o'`` circle marker
``'v'`` triangle_down marker
``'^'`` triangle_up marker
``'<'`` triangle_left marker
``'>'`` triangle_right marker
``'1'`` tri_down marker
``'2'`` tri_up marker
``'3'`` tri_left marker
``'4'`` tri_right marker
``'s'`` square marker
``'p'`` pentagon marker
``'*'`` star marker
``'h'`` hexagon1 marker
``'H'`` hexagon2 marker
``'+'`` plus marker
``'x'`` x marker
``'D'`` diamond marker
``'d'`` thin_diamond marker
``'|'`` vline marker
``'_'`` hline marker
'''
img9.png

img9.png

4.4 图片大小和保存

from matplotlib import pyplot as plt
import random
x = range(2,26,2) # x轴的位置
y = [random.randint(15, 30) for i in x]
# 设置图片的大小
'''
figsize:指定figure的宽和高,单位为英寸;
dpi参数指定绘图对象的分辨率,即每英寸多少个像素,缺省值为80 1英寸等于2.5cm,A4纸是 21*30cm的纸张
'''
# 根据画布对象
plt.figure(figsize=(20,8),dpi=80)

plt.plot(x,y) # 传入x和y, 通过plot画图
# plt.show()
# 保存(注意: 要放在绘制的下面,并且plt.show()会释放figure资源,如果在显示图像之后保存图片将只能保存空图片。)
plt.savefig('1.png')
# 图片的格式也可以保存为svg这种矢量图格式,这种矢量图放在网页中放大后不会有锯齿
# plt.savefig('1.svg')

4.5 绘制X轴和Y轴刻度

from matplotlib import pyplot as plt
import random
x = range(2,26,2) # x轴的位置
y = [random.randint(15, 30) for i in x]
plt.figure(figsize=(20,8),dpi=80)
# 设置x轴的刻度范围
# plt.xticks(x)
# plt.xticks(range(1,25))
# 设置y轴的刻度
# plt.yticks(y)
# plt.yticks(range(min(y),max(y)+1))
# 构造x轴刻度标签
x_ticks_label = ["{}:00".format(i) for i in x]
#rotation = 45 让字旋转45度
plt.xticks(x,x_ticks_label,rotation = 45)
# 设置y轴的刻度标签
y_ticks_label = ["{}℃".format(i) for i in range(min(y),max(y)+1)]
plt.yticks(range(min(y),max(y)+1),y_ticks_label)
# 绘图
plt.plot(x,y)
plt.show()
img10.png

img10.png

4.6 一图多线

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False

y1 = [random.randint(5, 20) for i in range(30)]
y2 = [random.randint(0, 50) for i in range(30)]

x = range(0,30)
# # 设置图形
plt.figure(figsize=(20,8),dpi=80)
'''
添加图例:label 对线的解释,然后用plt.legend添加到图片上;
添加颜色: color='red'
线条风格: linestyle='-'; - 实线 、 -- 虚线,破折线、 -. 点划线、 : 点虚线,虚线、 '' 留空或空格
线条粗细: linewidth = 5
透明度: alpha=0.5
'''
plt.plot(x,y1,color='red',label='股票A')
plt.plot(x,y2,color='blue',label='股票B')
# 设置x轴刻度
xtick_labels = ['{}日'.format(i+1) for i in x]
plt.xticks(x,xtick_labels,rotation=45)
# 绘制网格(网格也是可以设置线的样式)
#alpha=0.4 设置透明度
plt.grid(alpha=0.4)
# 添加图例(注意:只有在这里需要添加prop参数是显示中文,其他的都用fontproperties)
# 设置位置loc : upper left、 lower left、 center left、 upper center
plt.legend(loc='upper right')
#展示
plt.savefig('1.png')
img11.png

img11.png

4.7 多坐标系子图

# add_subplot方法----给figure新增子图
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(1, 100)
#新建figure对象
fig=plt.figure(figsize=(20,10),dpi=80)
#新建子图1
ax1=fig.add_subplot(2,2,1)
ax1.plot(x, x)
#新建子图2
ax3=fig.add_subplot(2,2,2)
ax3.plot(x, x ** 2)
ax3.grid(color='r', linestyle='--', linewidth=1,alpha=0.3)
#新建子图3
ax4=fig.add_subplot(2,2,3)
ax4.plot(x, np.log(x))
plt.show()

"""
#方法二:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1, 100)
#划分子图
fig,axes=plt.subplots(2,2)
ax1=axes[0,0]
ax2=axes[0,1]
ax3=axes[1,0]
ax4=axes[1,1]
fig=plt.figure(figsize=(20,10),dpi=80)
#作图1
ax1.plot(x, x)
#作图2
ax2.plot(x, -x)
#作图3
ax3.plot(x, x ** 2)
# ax3.grid(color='r', linestyle='--', linewidth=1,alpha=0.3)
#作图4
ax4.plot(x, np.log(x))
plt.show()
"""
img12.png

img12.png

5. 散点图

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False

import random
x = range(31) # x轴的位置
y = [random.randint(15, 26) for i in range(31)]

# 设置图形大小
plt.figure(figsize=(20,8),dpi=100)
# 使用scatter绘制散点图
plt.scatter(x,y,label= '3月份')
# 调整x轴的刻度
_xticks_labels = ['3月{}日'.format(i) for i in x]
plt.xticks(x[::3],_xticks_labels[::3],rotation=45)
plt.xlabel('日期')
plt.ylabel('温度')
# 图例
plt.legend()
plt.show()
img2.png

img2.png

6.柱状图

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False

a = ['流浪地球','疯狂的外星人','飞驰人生','大黄蜂','熊出没·原始时代','新喜剧之王']
b = [38.13,19.85,14.89,11.36,6.47,5.93]
plt.figure(figsize=(10,4),dpi=100)

# 绘制条形图的方法
width=0.3 # 条形的宽度
rects = plt.bar(range(len(a)),b,width=0.3,color='r')
plt.xticks(range(len(a)),a,rotation=45)
# 在条形图上加标注(水平居中)
for rect in rects:
    height = rect.get_height()
    plt.text(rect.get_x() + rect.get_width() / 2, height+0.3, str(height),ha="center")
plt.savefig("1.png")
img3.png

img3.png

7. 直方图

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False

time = [131, 98, 125, 131, 124, 139, 131, 117, 128, 108, 135, 138, 131, 102, 107, 114,
119, 128, 121, 142, 127, 130, 124, 101, 110, 116, 117, 110, 128, 128, 115, 99,
136, 126, 134, 95, 138, 117, 111,78, 132, 124, 113, 150, 110, 117, 86, 95, 144,
105, 126, 130,126, 130, 126, 116, 123, 106, 112, 138, 123, 86, 101, 99, 136,123,
117, 119, 105, 137, 123, 128, 125, 104, 109, 134, 125, 127,105, 120, 107, 129, 116,
108, 132, 103, 136, 118, 102, 120, 114,105, 115, 132, 145, 119, 121, 112, 139, 125,
138, 109, 132, 134,156, 106, 117, 127, 144, 139, 139, 119, 140, 83, 110, 102,123,
107, 143, 115, 136, 118, 139, 123, 112, 118, 125, 109, 119, 133,112, 114, 122, 109,
106, 123, 116, 131, 127, 115, 118, 112, 135,115, 146, 137, 116, 103, 144, 83, 123,
111, 110, 111, 100, 154,136, 100, 118, 119, 133, 134, 106, 129, 126, 110, 111, 109,
141,120, 117, 106, 149, 122, 122, 110, 118, 127, 121, 114, 125, 126,114, 140, 103,
130, 141, 117, 106, 114, 121, 114, 133, 137, 92,121, 112, 146, 97, 137, 105, 98,
117, 112, 81, 97, 139, 113,134, 106, 144, 110, 137, 137, 111, 104, 117, 100, 111,
101, 110,105, 129, 137, 112, 120, 113, 133, 112, 83, 94, 146, 133, 101,131, 116,
111, 84, 137, 115, 122, 106, 144, 109, 123, 116, 111,111, 133, 150]
# 2)创建画布
plt.figure(figsize=(20, 8), dpi=100)
# 3)绘制直方图
# 设置组距
distance = 2
# 计算组数
group_num = int((max(time) - min(time)) / distance)
# 绘制直方图
plt.hist(time, bins=group_num)
# 修改x轴刻度显示
plt.xticks(range(min(time), max(time))[::2])
# 添加网格显示
plt.grid(linestyle="--", alpha=0.5)
# 添加x, y轴描述信息
plt.xlabel("电影时长大小")
plt.ylabel("电影的数据量")
# 4)显示图像
plt.show()
img4.png

img4.png

8. 饼图

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False

label_list = ["第一部分", "第二部分", "第三部分"] # 各部分标签
size = [55, 35, 10] # 各部分大小
color = ["red", "green", "blue"] # 各部分颜色
explode = [0, 0.05, 0] # 各部分突出值
"""
绘制饼图
explode:设置各部分突出
label:设置各部分标签
labeldistance:设置标签文本距圆心位置,1.1表示1.1倍半径
autopct:设置圆里面文本
shadow:设置是否有阴影
startangle:起始角度,默认从0开始逆时针转
pctdistance:设置圆内文本距圆心距离
返回值
l_text:圆内部文本,matplotlib.text.Text object
kaikeba.com开课吧
精选领域名师,只为人才赋能 13
###
p_text:圆外部文本
"""
patches, l_text, p_text = plt.pie(size,
explode=explode,
colors=color,
labels=label_list,
labeldistance=1.1,
autopct="%1.1f%%",
shadow=False,
startangle=90,
pctdistance=0.6)
plt.axis("equal") # 设置横轴和纵轴大小相等,这样饼才是圆的
plt.legend()
plt.show()
img5.png

img5.png

关于作者

  • 姓名:

  • 微信:

  • Email:

  • GitHub: