AngularJS 页面载入事件的相关方法

AngularJS的uirouter一个重要的内容就是使用state的使用,这里记录一组关于state的事件,每当路由转移过程中发生对应事件时,都会在$rootScrope广播,使用$rootScope.$on('$stateEventName', function(event, ...))获取该事件。

0. 引入router.state.events

首先引入JavaScript源文件:

1
<script src="stateEvents.js"></script>

然后导入模块:

1
angular.module("myApplication", ['ui.router', 'ui.router.state.events']

1. 事件

  1. $stateChangeCancel
  2. $stateChangeStart
  3. $stateNotFound
  4. $stateChangeError
  5. $stateChangeSuccess

$stateChangeCancel

当一个页面的转移被取消时,会在$rootScope广播$stateChangeCancel事件
有以下参数可以使用:

  • toState 目标state
  • toParams 前往目标state时的参数
  • fromState 来源state
  • fromParams 带来的参数
  • options 选项
  • $transition$

和开始事件$stateChangeStart , 成功事件$stateChangeSuccess 提供的参数相同

$stateChangeError

相比于开始和成功的事件,错误事件多一个参数error,其内容当然是错误信息。

$stateNotFound

没有找到state时的事件,提供一下参数可以用:

  • unfoudState 未找到的state信息,提供to toParams options等属性
  • fromState 来源state
  • fromParams 来源参数
  • options 选项

2. 更新到 Transition Hooks

uirouter已经更新了IHookRegistry接口,可以对transition进行更为详细控制。
$transition$transitionService都实现了该接口,但是$transition对象只能在转换开始前使用。

该接口提供以下方法:

  1. getHooks 返回所有已登录的hook方法,参数时方法名,比如onBefore onEnter等等
  2. onBefore 开始前
  3. onStart 开始后
  4. onExit 退出,onStart之后transition会推出state
  5. onRetain 保留,onExit之后onRetain会被执行,子state会被优先执行
  6. onEnter 进入,在onRetain结束后,transition会进入state
  7. onFinish 结束前,这是最后可以取消或者定向transition的方法
  8. onError 错误
  9. onSuccess 成功

CSS 文字对齐

本来想直接把代码内嵌到markdown里,但是hexo好像不支持,残念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<table style="width:1000px">
<tr>
<th></th>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
<tr>
<th >0</th>
<td style="text-align: right;">text-align: right;</td>
<td style="text-align: left;">text-align: left;</td>
<td style="text-align: center;">text-align: center;</td>
</tr>
<tr style="height:100px;">
<th>1</th>
<td style="vertical-align: top;">vertical-align: top;</td>
<td style="vertical-align: bottom;">vertical-align: bottom;</td>
<td style="vertical-align: middle;">vertical-align: middle;</td>
</tr>
</table>

水平方向对齐:

  1. text-align: right;
  2. text-align: center;
  3. text-align: left;

垂直方向对齐:

  1. vertical-align: top;
  2. vertical-align: middle;
  3. vertical-align: bottom;

pipenv 基本的介绍和使用方法

根据项目需要,搞一个配置文件Pipfile,传到github里,完事儿。

为什么要用pipenv

首先,我们假设….算了,不扯那些没用的。做Python的开发,无论你用的什么工具,最麻烦的事儿应该莫过于包的管理。pipenv简单来说就是一个python官方推荐的包管理工具。
相比于pip等其他工具,pipenv最大的特点是可以根据项目,生成不同的环境,在ProjectA中设置的Pipfile不会影响到ProjectB,更不会影响到系统默认的pip列表。这个为项目定制的虚拟环境,我们称之为shell

安装pipenv

安装pipenv之前,首先确定系统里有以下两样东西:

  1. python
  2. pip

pipenv是通过pip安装的工具,所以执行

1
pip install pipenv 

由于pipenv可以在系统里不同版本的Python间任意切换,所以执行pip的时候不同太在乎Python版本问题。

requirements.txt 和 Pipfile

pipenv 管理Python环境的配置文件是Pipfile,大致长这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
pandas = "==0.25.1"

[requires]
python_version = "3.7"

我们可以直接写,也可以用pipenv install来生成默认的Pipfile, 不过还有个更方便的方法是直接由pip生成的requirements.txt配置文件来生成Pipfile。

1
2
3
4
5
6
7
8
# 首先在当前路径里使用pip生成requirements.txt

pip freeze > requirements.txt

# 然后生成Pipfile
# -r 的意思是将后面配置文件里的包添加到Pipfile里

pipenv install -r requirements.txt

指定Python版本

pipenv可以指定虚拟环境的python版本,随时通过修改Pipfile来切换(修改是通过命令行修改的意思,当然你要直接修改也没什么区别,但是不用命令来修改配置文件就会觉得很low啊)

1
2
3
4
5
6
7
8
9
10
## 使用下面的命令,从当前系统已经安装的不同版本的Python中选择一个
pipenv --python 版本号

## 比如
pipenv --python 3.5

pipenv --python 3.7

pipenv --python 2.6

如果Pipfile中指定的Python版本在当前系统下不存在,之后启动虚拟环境时会跳出警告。

(耗时警报) 锁定

更新Pipfile之后,启动虚拟环境之前,我们需要把Pipfile中列出的包安装好,或者删除已经不用的包,这个操作就是pipenv lock

由于需要从服务器下载相应的包(这是我猜的,鬼晓得为什么这玩意儿怎么那么慢,官方似乎也没有什么行之有效的办法)

去吧 皮卡丘!

都搞定之后,pipenv shell跑起来,我们就进入定制的虚拟环境了。

直接看这里就可以了

说了那么老多,其实安装好pipenv之后,直接一个pipenv -h查看help文档就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Options:
--where Output project home information.
--venv Output virtualenv information.
--py Output Python interpreter information.
--envs Output Environment Variable options.
--rm Remove the virtualenv.
--bare Minimal output.
--completion Output completion (to be eval'd).
--man Display manpage.
--support Output diagnostic information for use in GitHub issues.
--site-packages Enable site-packages for the virtualenv. [env var:
PIPENV_SITE_PACKAGES]
--python TEXT Specify which version of Python virtualenv should use.
--three / --two Use Python 3/2 when creating virtualenv.
--clear Clears caches (pipenv, pip, and pip-tools). [env var:
PIPENV_CLEAR]
-v, --verbose Verbose mode.
--pypi-mirror TEXT Specify a PyPI mirror.
--version Show the version and exit.
-h, --help Show this message and exit.


Usage Examples:
Create a new project using Python 3.7, specifically:
$ pipenv --python 3.7

Remove project virtualenv (inferred from current directory):
$ pipenv --rm

Install all dependencies for a project (including dev):
$ pipenv install --dev

Create a lockfile containing pre-releases:
$ pipenv lock --pre

Show a graph of your installed dependencies:
$ pipenv graph

Check your installed dependencies for security vulnerabilities:
$ pipenv check

Install a local setup.py into your virtual environment/Pipfile:
$ pipenv install -e .

Use a lower-level pip command:
$ pipenv run pip freeze

Commands:
check Checks for security vulnerabilities and against PEP 508 markers
provided in Pipfile.
clean Uninstalls all packages not specified in Pipfile.lock.
graph Displays currently-installed dependency graph information.
install Installs provided packages and adds them to Pipfile, or (if no
packages are given), installs all packages from Pipfile.
lock Generates Pipfile.lock.
open View a given module in your editor.
run Spawns a command installed into the virtualenv.
shell Spawns a shell within the virtualenv.
sync Installs all packages specified in Pipfile.lock.
uninstall Un-installs a provided package and removes it from Pipfile.
update Runs lock, then sync.

均方误差和决定系数

均方误差 Mean Square Error

是对误差的一个估计,其定义为所有估值和样本的误差平方的平均值。

在python中这样写:

1
2
3
4
from sklearn.metrics import mean_squared_error

mean_sqlared_error(df['data'],predict)

决定系数 Coefficient of determination / $R^2$

维基百科的定义是:用于度量因变量的变异中可由自变量解释部分所占的比例,以此来判断统计模型的解释力。

  • 首先得到一组因变量 取得平均值 $\bar{y}$
  • 由此可以得到总的方差 $SS_{tot}$
  • 再取得预估值的方差 $SS_{res}$
  • 最后得到决定系数 $R^2 = 1 - \frac{SS_{tot}}{SS_{res}}$

在python中这样写:

1
2
3
4
5
6
from sklearn.linear_model import LinearRegression

lm=LinearRegression()

lm.score(X,Y)

使用Python的线性回归分析

线性回归是啥?

线性回归是统计学中的一种回归分析方法,我们可以简单的记住线性回归中有两个重要变量:

  1. The predictor(independent) variable 自变量 x
  2. the target(dependent) variable 因变量 y

如果线性回归方程中只有一个x,则称为简单线性回归,大于一个x时,成为多元线性回归。
举个例子

1
y = b0 + b1*x

这个简单线性回归方程中,x是自变量,y是因变量,b0是intercept, b1叫做slope

Python中的简单线性回归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.linear_model import LinearRegression

lm=LinearRegression()

# 假设我们有一个dataframe df
X = df[['highway-mpg']] #作为自变量 X可以指定多列
Y = df['Price'] #因变量Y只有一列

lm.fit(X,Y) #使用线性回归分析

Yhat = lm.predict(X_0) # X_0 跟X又相同的列数

# 可以查看线性回归方程的参数
lm.intercept_

lm.coef_

关于Java的stream执行顺序的笔记

Java.util.stream 基本

首先来个例子:

1
2
3
Stream.of("aaa", "aa", "bbbb", "bb", "ccccc", "cc", "dddddd", "eeeeeee")
.filter(len -> len.length() > 3)
.forEach(System.out::println);

stream在使用的时候,分为中间操作(intermediate operation)和终端操作(terminal operation),例子中的filter()就是中间操作,中间操作可以有多个,跟火车厢一样接起来就可以。
而终端操作(或者叫终结操作)只能有一个。终端操作很容易理解成是对stream的结束,事实上终端操作最主要的作用是执行。如果没有终端操作,中间操作的内容将不会生效。所以,下面这个stream执行后是什么效果?

1
2
3
4
5
Stream.of("aaa", "aa", "bbbb", "bb", "ccccc", "cc", "dddddd", "eeeeeee")
.mapToInt(String::length)
.filter(len -> len > 3)
.peek(System.out::println)
.limit(2);

答案是:什么也不输出。因为limit()是个中间方法,对这个stream的操作将不会生效。

执行顺序

弄懂了什么中间操作和终端操作的区别以后,我们就要面对下一个疑问了:中间操作和终端操作的执行顺序是什么?
很简单,来个例子瞧瞧:

1
2
3
4
5
6
Stream.of("aaa", "aa", "bbbb", "bb", "ccccc", "cc", "dddddd", "eeeeeee")
.filter(len -> len.length() > 3)
.peek(System.out::println)
.mapToInt(String::length)
.limit(2)
.forEach(System.out::println);

输出结果有几行?内容是?

这里重点在peek()limit()forEach()的生效顺序,从结果上来看,应该是stream中的每一个元素,对应一组打包的操作,所以输出字符串b之后紧接着输出的是b的长度,而不是字符串c。而limit()限制了处理的元素个数,大于limit的元素到达不了终端操作,自然不会生效。

我们换个方法再验证一遍,这次终端操作用sum()来替代forEach(),终端操作不是对元素逐一的操作了,对中间操作的执行顺序会有什么影响吗?

1
2
3
4
5
6
7
Stream.of("aaa", "aa", "bbbb", "bb", "ccccc", "cc", "dddddd", "eeeeeee")
.filter(len -> len.length() > 3)
.peek(System.out::println)
.mapToInt(String::length)
.limit(2)
.peek(System.out::println)
.sum();

结果跟forEach()是一样的。

Log4j 节能写法

尽量使用Supplier<> 来生成log

比如debug,我们可以使用下面三种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* Logs a message object with the {@link Level#DEBUG DEBUG} level.
*
* @param message the message string to log.
*/
void debug(String message);

/**
* Logs a message with parameters at the {@link Level#DEBUG DEBUG} level.
*
* @param message the message to log; the format depends on the message factory.
* @param params parameters to the message.
* @see #getMessageFactory()
*/
void debug(String message, Object... params);

/**
* Logs a message with parameters which are only to be constructed if the logging level is the {@link Level#DEBUG
* DEBUG} level.
*
* @param message the message to log; the format depends on the message factory.
* @param paramSuppliers An array of functions, which when called, produce the desired log message parameters.
* @since 2.4
*/
void debug(String message, Supplier<?>... paramSuppliers);

第一种:直接输出信息。

最不环保的写法,甚至新手的话可能会出现logger.debug("A is " + a + ", B is" + b + ".");这种写法,首先使用+来生成log文本是非常耗时的,其次,无论log输出的级别是否低于debug,这段字符串的处理都会被执行。

第二种:使用MessageFactory和参数生成log

OpenJDK 8之前这么写是没问题的,使用MessageFactory来生成log既增加了可读性,又比字符串连接要节能。不过问题是log的生成还是总需要被执行。

第三种:使用Lambda

doc中写了:only to be constructed if the logging level is the DEBUG level.,还需要解释吗?

那么,当需要把objectA写到debug里时

1
2
3
4
5
logger.debug(objectA.toString()); // 一般写法

logger.debug(() -> objectA.toString()); // 略动脑筋的写法

logger.debug(objectA::toString); //文艺写法

HAS-A 和 IS-A的区别

一句话总结

不妨通过字面意思来理解,HAS-A 就是有什么的意思,而IS-A就是是什么的意思。在面向对象编程(OOP)中,类的继承就是IS-A的关系,简单来说就是“张三是个人,旺财是条狗”的意思。而类的对象使用,多数时候可以看作是HAS-A的关系,比如把身高,体重都看作是类的话,我们可以说“张三有身高,有体重”。

  • Inheritance 继承 IS-A
  • composition 组成 HAS-A

举个栗子

谷歌搜到的第一篇说明就很浅显易懂

1
2
3
4
5
6
7
8
9
Class Car{ ... } // 车是一个类 

Class Engine{ ... } // 引擎也是一个类

Class Toyota extends Car { // 丰田车作为一个有继承关系的子类,跟Car是IS-A关系,跟Engine是HAS-A的关系
private Engine;
...
}

Concurrency - Java的并发编程

Concurrency的意思是并发性,或者并行性(日语),顾名思义指的是同时处理不同的任务。并发编程的核心线程,可以简单地理解成RTS游戏里的“工人“单位:一个工人同一时间只能执行一个建造任务,增加工人可以增加建造序列的数量,也可以通过给工人切换建造任务来看起来增加建造序列,而这里的微操,全都是由系统来控制的。
所以,并发编程并不针对多核CPU,即使只有一个CPU,也可以通过分时段的线程切换来实现多个线程的并发执行。

Java的基本多线程写法

首先是线程的最基本写法。Java中有两种最基本的线程编写方法:

  1. 继承Thread类,在run()中书写程序内容,使用start()来启动线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Sample{
public static void main(String... args) {
WorkerA a = new WorkerA();
WorkerB b = new WorkerB();
// 这里我们有两个工人,可以同时启动两个建造任务了
a.start();
b.start();
}
}

class WorkerA extends Thread {
/**
* 在run里写上具体要给这个工人分配的任务
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.print("Yes, my lord. " + i);
}
System.out.println("Building cpmolete.");
}
}

class WorkerB extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.print("了解。" + i);
}
System.out.println("任務完了。");
}
}

  1. 实现Runnable接口。Thread的构造体可以接受一个Runnable的参数,所以我们也可以通过写一个实现了Runnable接口的类来生成线程。虽然也是在run方法中写所要执行的任务,但是实现Runnable的类是作为生成线程的参数来使用,所以我们可以理解为创建了一类工作,然后以该工作为参数创建(灵魂召唤)多个工人来执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  public class Sample {
public static void main(String[] args) {
// 我们创建了两个同样的工人,会执行同一种建造任务,比如都去造房子(卡人口不可取)
Work work = new Work();

Thread workerA = new Thread(work);
Thread workerB = new Thread(work);

workerA.start();
workerB.start();
}
}

class Work implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.print("Yes, my lord. " + i);
}
System.out.println("Building cpmolete.");
}
}

Runnable也可以使用Lambda表达式来定义,这样我们就可以直接写run方法的内容而不用去定义一整个类了。

1
2
3
4
5
6
Thread c = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("C " + i);
}
});
c.start();

线程的状态和优先级

java.lang的列举型Thread.State中定义了6中线程状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* A Thread which has not yet started.
*/
NEW,
/**
* A Thread which is running or suspended.
*/
RUNNABLE,
/**
* A Thread which is blocked on a monitor.
*/
BLOCKED,
/**
* A Thread which is waiting with no timeout.
*/
WAITING,
/**
* A Thread which is waiting with a timeout.
*/
TIMED_WAITING,
/**
* A thread which is no longer alive.
*/
TERMINATED

一个线程只能由start()启动一次,多次启动会产生IllegalThreadStateException

在Thread类中定义了三个优先级常数:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* The maximum priority value for a Thread.
*/
public final static int MAX_PRIORITY = 10; // Maximum allowed priority for a thread
/**
* The minimum priority value for a Thread.
*/
public final static int MIN_PRIORITY = 1; // Minimum allowed priority for a thread
/**
* The default priority value for a Thread.
*/
public final static int NORM_PRIORITY = 5; // Normal priority for a thread

线程的优先级可以通过priority的getter和setter来设置和确认,当两个进行中的线程发生冲突时,一般优先级高的线程会先运行,但这并不等于说优先级高的线程一定会被优先启动。系统在启动线程的时候并不看优先级的高低。

Thread类

Thread类提供了各种用来控制线程启动,暂停,再开,终止的方法,这里记录以下两个点的理解:

  1. InterruptedException. 当线程进入待机或者睡眠等状态时,就需要针对InterruptedException写一个try-catch,简单理解就是被人吵醒时应该怎么办。
  2. sleep可以设置一定的时间,但并不等于时间到了线程就会停止挂起,继续运行。sleep只是线程在不被interrupt的情况下最短的睡眠时长,时间到了之后,也要视系统的资源分配情况来决定什么时候继续运行。

同步与排他

并发编程一个很常见的问题就是不同线程对同一资源的读写控制,这里就要提到两个很基本的概念,同步和排他(锁)。
排他锁比较好理解,就是通过关键字或者方法来指定某一部分同一时间只能被一个线程使用,被调用时这部分资源会被锁定,其他想要使用该资源的线程则会被挂起,知道资源解锁,轮班使用。
synchronized是用来指定锁定资源的关键字,可以用来修饰方法,也可以用来指定代码块。

1
2
3
4
5
6
7
8
public synchronized void add(int a){...}

// 或者
public void add(int a){
synchronized(想要锁定的对象){
//指定的对象在这段代码运行过程中被上锁
}
}

同步则是指使用wait(),notify(),notifyAll()等方法,具体的控制线程的运行。
wait()可以指定也可以不指定超时的值,wait可以让线程进入等待,直到等待超时或者其他的线程使用了notify(),notifyAll()或者interrupt()方法,使等待的线程进入可执行的状态。
notify()会随机让一个等待的线程再开,notifyAll()则是让所有该对象的线程再开。
wait(),notify(),notifyAll()都是用来控制线程访问被锁资源的,所以如果没有synchronized锁定资源,这些方法的会抛出IllegalMonitorStateException

排他锁无法避免会出现不同的线程锁定不同的资源大家都干不了活儿的局面,这就是死锁;或者因为要使用的资源不断被其他线程占用而导致某一线程进展缓慢,这叫活锁。
死锁和活锁都不会在编译或者运行时报错,所以要留意设置超时,一定时间后手动解锁资源。等待资源访问时线程会处于饥饿的状态,饥饿的线程越多,则说明程序效率越低。为了减少饥饿,资源的调用顺序,锁定的粒度都需要好好思考。

并发集合

高效的线程管理

执行器

线程池

线程池的四种 BlockedQueue

多线程中的问题

内存释放 shutdown 和 shutdownNow

InterruptException

使用TimeUnit

Dockerfile 命令

内容整理自Docker入门到实践

什么是Dockerfile

Dockerfile 是一种用来定制镜像的文本文件。我们可以在Dockerfile中指定基础镜像,执行命令,开放端口,设置环境变量等等。
每一条指令会构建一层。参考docker 文档,docker的镜像具有下面的层结构(image layers),增加层的厚度自然会增加镜像的大小,所以写Dockerfile时,尽量使用\&& 巧妙地将命令的内容合并到一层。

比如Docker入门到实践中的例子,写一个构建nginx镜像的Dockerfile:

1
2
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

这个Dockerfile包含了两个指令,FROM制定了基础镜像,基础镜像可以是各种服务类的官方镜像,也可以是干净的操作系统镜像,还可以是空白镜像scratchRUN是最常用的指令,用来执行命令行命令。

接下来我们执行

1
docker build -t nginx:v3 .

来构建镜像,it nginx:v3的的意思是构建的目标名为nginx,标签为v3。

docker构建镜像的log将每一条指令分的很清楚,每一条指令作为一个step来执行,完成后我们就可以看到新的镜像nginx:v3了。

执行下面命令,通过新构建的镜像生成一个容器:

1
docker run --name hello -d -p 8080:80 nginx:v3

然后我们访问 localhost:8080 就可以看到欢迎页了:

Dockerfile中用到的命令

docker的指令一般有两种书写方式,一种是指令后跟命令行,一种是使用[]的函数调用写法,这里主要记录一下命令行写法。

  • COPY
    COPY <context path> <target path>
    这个指令将上下文目录中的文件或者目录复制到新一层的目标路径中,要复制的文件或者目录可以有多个,可以使用通配符(符合golang的filepath.Match规则),目标路径可以是绝对路径,也可以是工作目录的相对路径。
    还可以加上--chown=<user>:<group>来改变文件的所属用户和所属组

    上下文路径?context path?

  • ADD
    使用方法跟COPY基本一样。功能会丰富一点,比如源路径可以设置为url,使用url路径时docker会讲文件打包下载到目标路径。因为需要多余的RUN来处理权限,解压缩,筛选内容,这个命令并不是很实用。

  • CMD
    用于指定默认的容器主进程的启动命令。指令的写法也有两种:

    • shell 格式 CMD 命令
    • exec 格式 CMD ["可执行文件", "参数", ..."参数" ]

    使用shell格式写的内容会被解析为CMD ["sh", "-c", "命令"],所以命令中可以使用环境变量,但是命令必须前台执行,如果命令结束后直接结束了sh程序,则容器也会推出。

    Dockerfile中设置的启动命令可以在运行时重新指定,比如我们写docker run -it imagename的话,就会执行默认的命令,在镜像名后面添加命令,比如cat etc/os-release,就可以了。

  • ENTRYPOINT
    跟CMD一样用来设置启动时的命令行和参数,不过在重新定义的时候,要在docker run后面使用命令--entrypoint来设置。
    ENTRYPOINT和CMD共存的意义在于,定义了ENTRYPOINT之后,CMD的内容将不再被直接执行,而是作为参数传递给ENTRYPOINT。比如Docker入门-Dockerfile指令详解-NETRYPOINT入口点里面介绍的例子:

    1. 如果我们想在执行的时候为CMD [ "curl", "-s", "https://ip.cn" ]添加参数,直接在镜像名后面加肯定是不行的,那样会替换掉所有命令(全部重写那就另当别论了),如果使用CMD来获取参数,定义ENTRYPOINT [ "curl", "-s", "https://ip.cn" ],就可以实现参数的自由设置了。

    2. 用ENTRYPOINT来处理CMD做不到的工作。

      1
      2
      3
      4
      5
      6
      7
      8
      FROM alpine:3.4
      ...
      RUN addgroup -S redis && adduser -S -G redis redis
      ...
      ENTRYPOINT ["docker-entrypoint.sh"]

      EXPOSE 6379
      CMD [ "redis-server" ]

      docker-entrypoint.sh的内容为:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      #!/bin/sh
      ...
      # allow the container to be started with `--user`
      if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
      chown -R redis .
      exec su-exec redis "$0" "$@"
      fi

      exec "$@"

      这里将cmd作为参数获取的方法,在ENTRYPOINT中设置了一个脚本,通过参数的内容来区别启动用户的身份。

  • ENV
    定义环境变量,就这么简单。可以用=,可以用空格,可以用等号加空格一行定义一群,也可以用\加入换行。

  • ARG
    定义参数。参数和环境变量不同的是,参数只在构建的时候有用,在容器运行时不会被引入容器内部。参数的定义可以在docker build时使用--build-arg <name>=<value>的格式来覆盖。

  • VOLUME
    匿名卷

  • EXPOSE
    暴露端口

  • WORKDIR
    指定工作目录

  • USER
    指定当前用户

  • HEALTHCHECK
    健康检查

  • ONBUILD
    构建时执行

镜像的使用

镜像的获取,管理和删除

commit 命令