PPT经验

PPT使用技巧(转)

(2011-06-16 08:48:17)

ppt里的备注可以一次性删除吗?

可以。用以下两个宏都可实现(注:本博未测试,全是转载)。

1

1
2
3
4
dim sld as slide
for each sld in activepresentation.slides
sld.notespage.shapes.placeholders(2).textframe.textrange.text = ""
next

此宏已在ppt 2003上运行成功,速度极快(零点几秒),不管有多少张幻灯片,瞬间删除所有备注页内容。

2 下面这个宏会提示,把备注保存为txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Sub DeleteNote()
Dim actppt As Presentation
Dim pptcount As Integer
Dim iChose As Integer
Dim bDelete As Boolean
Dim sMsgBox As String
Dim dirpath As String
Dim txtstring As String

sMsgBox = "运行该宏之前,请先作好备份!继续吗?"

iChoice = MsgBox(sMsgBox, vbYesNo, "备份提醒")

If iChoice = vbNo Then

Exit Sub

End If

sMsgBox = "导出备注后,需要删除PPT备注吗?"

iChoice = MsgBox(sMsgBox, vbYesNo, "导出注释")

If iChoice = vbNo Then

bDelete = False

Else

bDelete = True

End If

Set actppt = Application.ActivePresentation

dirpath = actppt.Path & "" & actppt.Name & " 的备注.txt"

pptcount = actppt.Slides.Count

'打开书写文件

Set fs = CreateObject("Scripting.FileSystemObject")

Set a = fs.CreateTextFile(dirpath, True)

'遍历ppt

With actppt

For i = 1 To pptcount

txtstring = .Slides(i).NotesPage.Shapes.Placeholders(2).TextFrame.TextRange.Text

If (bDelete) Then

.Slides(i).NotesPage.Shapes.Placeholders(2).TextFrame.TextRange.Text = ""

End If

a.writeline (.Slides(i).SlideIndex)

a.writeline (txtstring)

a.writeline ("")

Next i

End With

a.Close

End Sub

Office PowerPoint PPT 2007 批量删除备注页

2009年08月16日 星期日 04:06

http://hi.baidu.com/anlaflasher/blog/item/c326918f1c6f2ce7f11f3659.html

为了对演示文稿中的内容有所把握,演示文稿的作者或审阅者往往会在PowerPoint中添加备注信息。这些备注信息可以提示演讲者在每页幻灯片中要演讲的内容,并可以在幻灯片放映时显示在演示者视图中。但是,如果希望将演示文稿发送给其他人,这些备注信息可能就不宜发布了。那么,如何能够快速将演示文稿中每页幻灯片的备注信息一次性全部删除呢?在Microsoft PowerPoint 2007中,通过非常简单的操作就可以实现。

(1)单击【Office按钮】,并从下拉菜单中执行【准备】|【检查文档】命令,打开“文档检查器”对话框。

(2)选中“演示文档备注”复选框,以检查演示文档中演示者备注的信息,然后单击【检查】按钮。

(3)在检查结果中,文档检查器检查到文档中有演示文稿的备注,单击右侧的【全部删除】按钮即可删除该演示文稿所有幻灯片的备注信息。

提示:文档检查器也可以在Word 2007和Excel 2007中运行。

另附2003版实现方式(宏的使用):

1
2
3
4
dim sld as slide
for each sld in activepresentation.slides
sld.notespage.shapes.placeholders(2).textframe.textrange.text = ""
next

此宏本博未经测试,如有兴趣者可自行测试,据说此宏已在ppt 2003上运行成功,速度极快(零点几秒),不管有多少张幻灯片,瞬间删除所有备注页内容。

分屏演示的好处有:

  1. 可以看到演示耗时,便于掌握时间进度。
  2. 可以看到前后的胶片,知道下一张讲什么内容。
  3. 方便的黑屏按钮,点击后变为继续。

分屏演示的具体做法也很简单:

1、确认电脑和投影仪已打开,并已连接好。但注意:先不要将电脑切换到投影仪,否则后面的步骤无法做。
2、桌面-右键-属性,如下图,选设置,点中屏幕2,勾选“将windows桌面扩展到该监视器”,确定。
3、在PPT里,选菜单 幻灯片放映-设置放映方式,如下图,选择监视器2,勾选 “显示演示者视图”,确定。4、点放映按钮,这时电脑上会出现演示者视图,投影仪上也自动出现了演示画面!

转的PPT经验,需修改删除

1、Magic Seven原则(7士2=5~9)。每张幻灯片传达5个概念效果最好。 7个概念人脑恰恰好可以处理。超过9个概念负担太重了,请重新组织。

如果你能做到一张幻灯片能表明一个问题最好,做到这一点,不在于你的PPT技术,而在于你对所演示内容的掌握,只有对内容熟悉,才能做到精简,否则你觉得什么内容都很重要,什么都不可以去掉,所以做PPT,内容才是根本。

2、KISS (Keep It Simple and Stupid)原则。因为我们做PPT针对的是大众,不是小众。我们的目的是把自己的理解灌输给听众。深入浅出才代表你对知识的真正掌握。

记住人才是主角,万万不能让PPT代替你的存在,除非你在写一个说明文档,或者你希望别人利用PPT就能达到目标,否则你就应当多准备些东西,如何解释你的PPT,可以使用试试幻灯片的备注,不过我没有用过这种方式。

3、10/20/30法则。演示文件不超过10页,演讲时间不超过20分钟,演示使用的字体不小于30号(30 point)。

只是约数,没有这么认真,尽可能而不是绝对。

个人觉得这些有指导意义,但经验感和技术感太强。也没有说清楚为什么要这样做。我更愿意接受“利用PPT作为工具控制观众的眼球和注意力”的说法。自己想的。

4、能用图表就用图表。所有的人都会先挑图看。所有人看到图表,第一眼就是找最低的和最高的,然后找跟自己相关的。把这三个东西标出来,人家会觉得很省事。

没有图表,怎么办?

图表也依赖于你对内容的理解,你理解了,就可以找出对比,有对比,有就图表存在的可能。

5、别写那么多字,没人看除非你打算照着念。要想办法让人知道你的PPT还有多少,或者告诉人家你要说的条理和结构。这非常重要,对自己好也对观众好。

6、不要用超过3种的动画效果,包括幻灯片切换。好的PPT不是靠效果堆砌出来的,朴素一点比花哨的更受欢迎。

简洁是我的第一审美原则,追求动态与色彩效果,是初学者很乐意尝试的东西,但这不是根本,除非必要,不要使用动态的东西,我从不认为动态效果或GIF可以增加效果,很多时候,它所起到的都是负面效果,动画只有本身含有所要表达的信息时,其存在才是有意思的。

7、多用口语,放在一些类似tips的地方,效果往往加倍。

8、字体大小建议:大标题 44 点 粗体;标题一 32点 粗体;标题二 28点 粗体;标题三 24点 粗体

9、PPT的灵魂—-“逻辑!”12字真言:“能用图,不用表;能用表,不用字”

10、不能图片太多、太鲜艳,ppt目的是让别人看到文字,而不是图片,求花哨容易分散观看者的注意力。

11、尽量选择深色背景配浅色字体,或者浅色背景配深色字体,对比强一些,因为如果用颜色相近的背景和字体,会看不清楚。

12、不要把全屏都铺满文字,演示是提纲挈领式的,显示内容越精炼越好。

13、为文字内容设定动画,一步一步显示,有利于讲演。这是动画的另一种作用,控制内容的呈现方式和速度。

制作技巧:欢迎大家对此部分内容多提供些内容。

  1. 使用母版,如果你不知道幻灯片母版的概念和作用及其如何使用,那你就不能说会PPT,一定要去学习母版,会给你意想不到的效果。

  2. 使用样式,而不是随意设定对像的格式,把其设置成样式。

  3. 使用默认格式,你要画一个线吗?是每一次都设置一下格式,你发现了默认格式选项吗?


1、两幅图片同时动作。PowerPoint的动画效果比较多,但图片只能一幅一幅地动作。如果你有两幅图片要一左一右或一上一下地向中间同时动作,可就麻烦了。其实办法还是有的,先安置好两幅图片的位置,选中它们,将之组合起来,成为”一张图片”。接下来将之动画效果设置为”左右向中间收缩”,现在请看一看,是不是两幅图片同时动作了?

2、滚动文本框的制作。右击工具栏打开”控件工具箱”,再点击文本框,而后从”属性”里面把滚动条打开,在TEXT里面输入文本框的内容.(完成)还可以通过”其他控件”中的SHOCKWAVE FLASH OBJECT 实现PPT中加入FLASH。

3、”格式”中的”幻灯片设计”中的”配色方案”中的”编辑配色方案”可以对文本及链接进行颜色重改,要使链接不带有下划线只要通过新建一个文本层来文本层来实现链接就可以了。

4、PPT制作三维效果的确也挺不错的.只要利用”三维效果设置”面板就可以基本完成。

5、在段落中另起新行而不用制表位。缩进和制表位有助于对齐幻灯片上的文本。对于编号列表和项目符号列表,五层项目符号或编号以及正文都有预设的缩进。但有时用户可能要在项目符号或编号列表的项之间另起一个不带项目符号和编号的新行。这个新行仍然是它上面段落的一部分,但是它需要独占一行。这时用户只需要 Shift Enter,即可另起新行。用户要注意,一定不能直接使用Enter,这样软件会自动给下一行添上制表位。

6、将Word文件导入PowerPoint。用户只需要先将Word文件转换为大纲视图,设置好各级的标题,然后在PowerPoint中执行“插入”菜单中的“幻灯片(从大纲)”命令,在弹出的对话框中选择想要要转换为幻灯片的WORD文档就可以了。

7、在幻灯片的任何位置上添加日期、时间。在在幻灯片上,定位占位符或文本框内的插入点。点击“插入→日期和时间”,系统弹出“日期和时间”对话框,用户可以选择自己喜欢的时间格式。选完以后单击“确定”就可以了。

使用技巧:

1、打包。”打包”功能用于压缩演示文稿(包括所使用的特殊字体文件、链接的文档和多媒体文件),帮助用户轻松地复制到软盘上以便携带。打包过程:单击”文件/打包”菜单项→选择需要打包的演示文稿→依”打包”向导的步骤完成打包任务。

2、将PPT文件的扩展名改为PPS可以将PPS文件可直接播放。

3、你可能不知道的快捷键:B键:黑屏,W键:白屏,在播放的PPT中使用画笔标记:CTRL P,擦除所画的内容:E键。

4、F5 放映文档

5、使用wps2007中的演示文档代替microsoft office的powerpooint,更小,更快!

附:一些小技巧:

在PPT演示文稿内复制幻灯片

要复制演示文稿中的幻灯片,请先在普通视图的“大纲”或“幻灯片”选项中,选择要复制的幻灯片。如果希望按顺序选取多张幻灯片,请在单击时按Shift 键;若不按顺序选取幻灯片,请在单击时按Ctrl键。然后在“插入”菜单上,单击“幻灯片副本”,或者直接按下“Ctrl shift D”组合键,则选中的幻灯片将直接以插入方式复制到选定的幻灯片之后。

Powerpoint自动黑屏

在用Powerpoint展示课件的时候,有时需要学生自己看书讨论,这时为了避免屏幕上的图片影响学生的学习注意力可以按一下“B”键,此时屏幕黑屏。学生自学完成后再接一下“B”键即可恢复正常。按“W”键也会产生类似的效果。

将幻灯片发送到word文档

1、在Powerpoint中打开演示文稿,然后在“文件”菜单上,指向“发送”,再单击“Microsoft Word”。
2、在“将幻灯片添加到Microsoft word文档”之下,如果要将幻灯片嵌入word文档,请单击“粘贴”;如果要将幻灯片链接到word文档,请单击“粘贴链接”。如果链接文件,那么在Powerpoint中编辑这些文件时,它们也会在word文档中更新。
3、单击“确定”按钮。此时,系统将新建一个word文档,并将演示文稿复制到该文档中。如果word未启动,则系统会自动启动word。

让幻灯片自动播放

要让powerpoint的幻灯片自动播放,只需要在播放时右键点击这个文稿,然后在弹出的菜单中执行“显示”命令即可,或者在打开文稿前将该文件的扩展名从PPT改为PPS后再双击它即可。这样一来就避免了每次都要先打开这个文件才能进行播放所带来的不便和繁琐。

增加PPT的“后悔药”

在使用powerpoint编辑演示文稿时,如果操作错误,那么只要单击工具栏中的“撤消”按钮,即可恢复到操作前的状态。然而,默认情况下 Powerpoint最多只能够恢复最近的20次操作。其实,powerpoint允许用户最多可以“反悔”150次,但需要用户事先进行如下设置:在 “工具-选项”,击“编辑”选项卡,将“最多可取消操作数”改为“150”,确定。

PPT中的自动缩略图效果

你相信用一张幻灯片就可以实现多张图片的演示吗?而且单击后能实现自动放大的效果,再次单击后还原。其方法是:
新建一个演示文稿,单击“插入”菜单中的“对象”命令,选择“Microsoft powerpoint演示文稿”,在插入的演示文稿对象中插入一幅图片,将图片的大小改为演示文稿的大小,退出该对象的编辑状态,将它缩小到合适的大小,按F5键演示一下看看,是不是符合您的要求了?接下来,只须复制这个插入的演示文稿对象,更改其中的图片,并排列它们之间的位置就可以了。

快速灵活改变图片颜色

利用powerpoint制作演示文稿课件,插入漂亮的剪贴画会为课件增色不少。可并不是所有的剪贴画都符合我们的要求,剪贴画的颜色搭配时常不合理。这时我们右键点击该剪贴画选择“显示’图片’工具栏”选项(如果图片工具栏已经自动显示出来则无需此操作),然后点击“图片”工具栏上的“图片重新着色”按钮,在随后出现的对话框中便可任意改变图片中的颜色。

用powerpoint为公司做演示文稿时,最好第一页都加上公司的Logo,这样可以间接地为公司做免费广告。执行“视图-母版-幻灯片母版”命令,在 “幻灯片母版视图”中,将Logo放在合适的位置上,关闭母版视图返回到普通视图后,就可以看到在每一页加上了Logo,而且在普通视图上也无法改动它了。

“保存”特殊字体

为了获得好的效果,人们通常会在幻灯片中使用一些非常漂亮的字体,可是将幻灯片拷贝到演示现场进行播放时,这些字体变成了普通字体,甚至还因字体而导致格式变得不整齐,严重影响演示效果。
在powerpoint中,执行“文件-另存为”,在对话框中点击“工具”按钮,在下拉菜单中选择“保存选项”,在弹出其对话框中选中“嵌入TrueType字体”项,然后根据需要选择“只嵌入所用字符”或“嵌入所有字符”项,最后点击“确定”按钮保存该文件即可。

利用组合键生成内容简介

我们在用powerpoint2003制作演示文稿时,通常都会将后面几个幻灯片的标题集合起来,把它们作为内容简介列在首张或第二张幻灯片中,让文稿看起来更加直观。如果是用复制粘贴来完成这一操作,实在有点麻烦,其实最快速的方法就是先选择多张幻灯片,接着按下alt shift s即可。

演示文稿中的图片随时更新

在制作演示文稿中,如果想要在其中插入图片,执行“插入-图片-来自文件”,然后打开“插入图片”窗口插入相应图片。其实当我们选择好想要插入的图片后,可以点击窗口右侧的“插入”按钮,在出现的下拉列表中选“链接文件”项,点击确定。这样一来,往后只要在系统中对插入图片进行了修改,那么在演示文稿中的图片也会自动更新,免除了重复修改的麻烦。

快速调用其他PPT

在进行演示文档的制作时,需要用到以前制作的文档中的幻灯片或要调用其他可以利用的幻灯片,如果能够快速复制到当前的幻灯片中,将会给工作带来极大的便利。
在幻灯片选项卡时,使光标置于需要复制幻灯片的位置,选择“菜单”中的“幻灯片(从文件)”命令,在打开的“幻灯片搜索器”对话框中进行设置。
通过“浏览”选择需要复制的幻灯片文件,使它出现在“选定幻灯片”列表框中。选中需要插入的幻灯片,单击“插入”,如果需要插入列表中所有的幻灯片,直接点击“全部插入”即可。这样,其他文档中的幻灯片就为我们所用了。

快速定位幻灯片

在播放powerpoint演示文稿时,如果要快进到或退回到第5张幻灯片,可以这样实现:按下数字5键,再按下回车键。若要从任意位置返回到第1张幻灯片,还有另外一个方法:同时按下鼠标左右键并停留2秒钟以上。

利用剪贴画寻找免费图片

当我们利用powerpoint2003制作演示文稿时,经常需要寻找图片来作为铺助素材,其实这个时候用不着登录网站去搜索,直接在“剪贴画”中就能搞定。方法如下:插入-图片-剪贴画,找到“搜索文字”一栏并键入所寻找图片的关键词,然后在“搜索范围”下拉列表中选择“Web收藏集”,单击“搜索”即可。这样一来,所搜到的都是微软提供的免费图片,不涉及任何版权事宜,大家可以放心使用。

制作滚动文本

在powerpoint中有时因显示文本内容较多就要制作滚动文本。具体制作方法如下:视图-工具栏-控件箱,打开控件工具箱,点击“文字框”选项,插入 “文字框”控件,然后在幻灯片编辑区按住鼠标左键拖拉出一个文本框,并根据版面来调整它的位置和大小。接着在“文字框”上右击鼠标,选择快捷菜单中的“属性”命令,弹出“文字框”属性窗口,在属性窗口中对文字框的一些属性进行相关的设置。
设置好后右击“文字框”,选择“文字框对象”中的“编辑”命令,这时就可以进行文字的输入,文本编辑完之后,在文字框外任意处单击鼠标,即可退出编辑状态。一个可以让框内文字也随滚动条拖动而移动的文本框就做好了。

突破20次的撤消极限

Powerpoint 的“撤消”功能为文稿编辑提供了很大方便。但powerpoint默认的操作次数却只有20次。执行“工具-选择”,击“编辑”标签卡,在“最多可取消操作数”中设置你需要的次数即可。不过要注意,powerpoint撤消操作次数限制最多为150次。

利用画笔来做标记

利用powerpoint2003放映幻灯片时,为了让效果更直观,有时我们需要现场在幻灯片上做些标记,这时该怎么办?在打开的演示文稿中单击鼠标右键,然后依次选择“指针选项-绘图”即可,这样就可以调出画笔在幻灯片上写写画画了,用完后,按ESC键便可退出。

快速调节文字大小

在powerpoint中输入文字大小不合乎要求或者看起来效果不好,一般情况是通过选择字体字号加以解决,其实我们有一个更加简洁的方法。选中文字后按ctrl ]是放大文字,ctrl [是缩小文字。

计算字数和段落

执行“文件-属性”,在其对话框中选“统计”选项卡,该文件的各种数据,包括页数、字数、段落等信息都显示在该选项卡的统计信息框里。

轻松隐藏部分幻灯片

对于制作好的powerpoint幻灯片,如果你希望其中的部分幻灯片在放映时不显示出来,我们可以将它隐藏。方法是:在普通视图下,在左侧的窗口中,按 Ctrl,分别点击要隐藏的幻灯片,点击鼠标右键弹出菜单选“隐藏幻灯片”。如果想取消隐藏,只要选中相应的幻灯片,再进行一次上面的操作即可。

将图片文件用作项目符号

一般情况下,我们使用的项目符号都是1、2、3,a、b、c之类的。其实,我们还可以将图片文件作为项目符号,美化自己的幻灯片。首先选择要添加图片项目符号的文本或列表。点击“格式-项目符号和编号”,在“项目符号项”选项卡中单击“图片”,调出剪辑管理器,你就可以选择图片项目符号。在“图片项目符号”对话框中,单击一张图片,再单击确定。

对象也用格式刷

在powerpoint中,想制作出具有相同格式的文本框(比如相同的填充效果、线条色、文字字体、阴影设置等),可以在设置好其中一个以后,选中它,点击“常用”工具栏中的“格式刷”工具,然后单击其它的文本框。如果有多个文本框,只要双击“格式刷”工具,再连续“刷”多个对象。完成操作后,再次单击 “格式刷”就可以了。其实,不光文本框,其它如自选图形、图片、艺术字或剪贴画也可以使用格式刷来刷出完全相同的格式。

幻灯片放映时让鼠标不出现Powerpoint幻灯片在放映时,有时我们需要对鼠标指针加以控制,让它一直隐藏。方法是:放映幻灯片,单击右键,在弹出的快捷菜单中选择“指针选项-箭头选项”,然后单击“永远隐藏”,就可以让鼠标指针无影无踪了。如果需要“唤回”指针,则点击此项菜单中的“可见”命令。如果你点击了“自动”(默认选项),则将在鼠标停止移动3秒后自动隐藏鼠标指针,直到再次移动鼠标时才会出现。
改变链接文字的默认颜色

Powerpoint2003 中如果对文字做了超链接或动作设置,那么powerpoint会给它一个默认的文字颜色和单击后的文字颜色。但这种颜色可能与咱们预设的背景色很不协调,想更改吗?那么可以点击菜单命令“格式-幻灯片设计”,在打开的“幻灯片设计”任务窗格下方的“编辑配色方案…”。在弹出的 “编辑配色方案”对话框中,点击“自定义”选项卡,然后就可以对超链接或已访问的超链接文字颜色进行相应的调整了。

快速切换输入法

大家知道在excel中通过设置可以方便地切换输入法,难道在powerpoint中就没有这样的功能吗?事实上是有这种功能的:单击“工具”菜单中的 “选项”命令,切换到“编辑”选项卡,选中“’自动键盘’切换”复选框,单击确定即可。该设置对已有文件的编辑非常方便,但在编辑新文件时却起不了多大的作用。

巧用文字环绕方式

在Powerpoint/2003中,我们在插入剪贴画之后可以将它自由旋转,但在word2003中将剪贴画插入后却不可以这样旋转。其实,我们只须选中插入的剪贴画,然后在出现的“图片”工具栏中点击“文字环绕”按钮,在弹出的文字环绕方式中选择除“嵌入型”以外的其它任意一种环绕方式,该剪贴画就可以进行自由旋转了。此外,如果我们先在Powerpoint中插入剪贴画,然后将它剪切到word中,也可以直接将它进行自由旋转。

快速选择多个对象

在powerpoint2003 中,如果要选择叠放在一起的若干个对象时会不太容易,特别是它们又位于叠放次序下层的时候,更是如此。不过,我们可以点击 “绘图”工具栏右侧的三角箭头(工具栏选项),依次指向“添加或删除按钮-绘图”,然后选中“选中多个对象”,将它添加到“绘图”工具栏中,点击它,会打开“选择多个对象”对话框。我们只要在对话框的对象列表中选中相应的那些对象就可以了。这个按钮的添加也可以这么做:点击菜单命令“工具-自定义”,在打开的对话框中点击“命令”选项卡,然后在“类别”中选“绘图”,在“命令”栏中选择“选中多个对象”,将它拖至工具栏的任意位置。

打造多彩公式

在powerpoint 中也可以使用公式编辑器插入公式。但默认的公式都是黑颜色的,与我们演示文稿的背景很不协调。其实,我们可以选中编辑好的公式,然后点击右键,在弹出的快捷菜单中选择“显示’图片’工具栏”命令。再点击“图片”工具栏中的“图片重新着色”按钮,就可以在打开的对话框中为公式指定其他的任意颜色了。

灵活设置背景

大家可以希望某些幻灯片和母版不一样,比如说当你需要全屏演示一个图表或者相片的时候。你可以进入“格式”菜单,然后选择“背景”,选择“忽略母版背景图形”选项之后,你就可以让当前幻灯片不使用母版背景。

防止被修改

在powerpoint中点击“工具-选项-安全性”,然后设置“修改权限密码”即可防止PPT文档被人修改。另外,还可以将PPT存为PPS格式,这样双击文件后可以直接播放幻灯片。

看看powerpoint的特殊播放模式

播放PPT文档时,点击Powerpoint的“幻灯片放映”菜单中的“观看幻灯片”将启动默认的全屏放映模式,这时必须使用“Alt Tab”或 “Alt Esc”组合键才能与其他窗口切换。如果在播放幻灯片时,按住Alt键不放,依次按下“D”、“V”键激活播放操作,就可以让幻灯片放映模式变成一个带标题栏和菜单栏的普通窗口形式,操作起来就方便多了。

去掉链接文字的下划线

向PPT文档中插入一个文本框,在文本框输入文字后,选中整个文本框,设置文本框的超链接。这样在播放幻灯片时就看不到链接文字的下划线了。

窗口模式下播放PPT

在按住Alt键不放的同时,依次按D和V键即可。这个时候就可在窗口模式下播放PPT了。

一次性展开全部菜单

由于工作的关系,经常使用powerpoint发现它的菜单很是麻烦,想全部展开所有菜单项,必须单击菜单中向下的双箭头。后来打开“工具-自定义”,单击“选项”选项卡,选定“始终显示整个菜单”复选框,再单击“关闭”按钮就可以一次性展开全部菜单了。

巧用键盘铺助定位对象

在PPT中有时候用鼠标定位对象不太准确,按住Shift键的同时用鼠标水平或竖直移动对象,可以基本接近于直线平移。在按住Ctrl键的同时用方向键来移动对象,可以精确到像素点的级别。

巧让多对象排列整齐

在某幻灯片上插入了多个对象,如果希望快速让它们排列整齐,按住Ctrl键,依次单击需要排列的对象,再选择“绘图-对齐或分布”,最后在排列方式列表中任选一种合适的排列方式就可实现多个对象间隔均匀的整齐排列。

打印清晰可读的PPT文档

通常PPT文稿被大家编辑得图文声色并茂,但若把这样的演示文稿用黑白打印机打印出来,可读性就较差。以下的方法,可以让你用黑白打印机打印出清晰可读的演示文稿:
首先点击“工具-选项”命令,单击“打印”选项卡,在“此文档的默认打印设置”标题下,单击“使用下列打印设置”,然后在“颜色/灰度”框中,单击“纯黑白”。
确定后在“颜色/灰度”框中选择“灰度”模式是在黑白打印机上打印彩色幻灯片的最佳模式,此时将以不同灰度显示不同彩色格式;选择“纯黑白”模式则将大部分灰色阴影更改为黑色或白色,可用于打印草稿或清晰可读的演讲者备注和讲义;选择“颜色”模式则可以打印彩色演示文稿,或打印到文件并将颜色信息存储在 *.prn文件中。当选择“颜色”模式时,如果打印机为黑白打印机,则打印时使用“灰度”模式。

将声音文件无限制打包到PPT文件中

幻灯片打包后可以到没有安装PPT的电脑中运行,如果链接了声音文件,则默认将小于100KB的声音素材打包到PPT文件中,而超过该大小的声音素材则作为独立的素材文件。其实我们可以通过设置就能将所有的声音文件一起打包到PPT文件中。方法是:单击“工具-选项-常规”,将“链接声音文件不小于 100KB”改大一点,如“50000KB”(最大值)就可以了。

PPT编辑放映两不误

能不能一边播放幻灯片,一边对照着演示结果对幻灯进行编辑呢?答案是肯定的,只须按住Ctrl不放,单击“幻灯片放映”菜单中的“观看放映”就可以了,此时幻灯片将演示窗口缩小至屏幕左上角。修改幻灯片时,演示窗口会最小化,修改完成后再切换到演示窗口就可看到相应的效果了。

将PPT演示文稿保存为图片

大家知道保存幻灯片时通过将保存类型选择为“Web页”可以将幻灯片中的所有图片保存下来,如果想把所有的幻灯片以图片的形式保存下来该如何操作呢?
打开要保存为图片的演示文稿,单击“文件-另存为”,将保存的文件类型选择为“JPEG文件交换格式”,单击“保存”按钮,此时系统会询问用户“想导出演示文稿中的所有幻灯片还是只导出当前的幻灯片?”,根据需要单击其中的相应的按钮就可以了。

PPT图表也能用动画展示

Powerpoint中的图表是一个完整的图形,如何将图表中的各个部分分别用动画展示出来了呢?其实只须右击图表,选择“组合”中的“取消组合”就可以将图表拆分开来了,接下来就可以对图表中的每个部分分别设置动作。

PPT中视图巧切换

当你点击“普通视图”按钮时如果按下Shift键就可以切换到“幻灯片母版视图”;再点击一次“普通视图”按钮(不按shift键)则可以切换回来。而如果点击“幻灯片浏览视图”按钮时,按下shift键就可以切换到“讲义母版视图”。

PPT2007:隐藏重叠的图片

如果在幻灯片中插入很多精美的图片,在编辑的时候将不可避免地重叠在一起,妨碍我们工作,怎样让它们暂时消失呢?方法如下:
首先单击“开始”选项卡,找到“编辑”功能组,再点击“选择→选择窗格”,在工作区域的右侧会出现“选择和可见性”窗格。在此窗格中,列出了所有当前幻灯片上的“形状”,并且在每个“形状”右侧都有一个“眼睛”的图标,单击想隐藏的“形状”右侧的“眼睛”图标,就可以把档住视线的“形状”隐藏起来了。

思路!做PPT最需要的就是思路

自认做PPT我已经是很高很高的高手了(自恋中…),做了8、9年,技术上没有任何问题,可是每次在做新PPT之前,还是很头痛!

头痛的东西很多:思路,架构,版面,色调,详略,图片等等.

但感觉最有用也是最头痛的就是思路.

由此,根据我的经验,我认为做PPT的流程应该是:

1、最开始什么都不要想,不要去查资料,也不要接触电脑,而是用笔在纸上写出提纲,当然,能简单的划出逻辑结构图最好了.越细越好.

2、打开PPT,不要用任何模板,将你的提纲按一个标题一页整出来.(过去我就总是追求完美,首先搞摸板,花掉半个多小时,做的过程中不满意又修改,做完后又修改,甚至最后完全推翻—-伤神费力耗时!!)

3、有了整篇结构性的PPT(底版/内容都是空白的,只是每页有一个标题而已),就可以开始去查资料了,将适合标题表达的内容写出来或从网上拷贝进来,稍微修整一下文字,每页的内容做成带”项目编号”的要点.当然在查阅资料的过程中,可能会发现新的资料,非常有用,却不在你的提纲范围中,则可以进行调整,在合适的位置增加新的页面.

4、看看PPT中的内容哪些是可以做成图的,如其中中带有数字、流程、因果关系、障碍、趋势、时间、并列、顺序等等内容的,全都考虑用图画的方式来表现。如果有时候内容过多或实在是用图无法表现的时候,就用“表格”来表现。实在实在是不行了,才用文字说明。所以,最好的表现顺序是:图–表–字。这个过程中图是否漂亮不要在意,“糙”点没关系,关键是你用的图是否准确。

5、选用合适的母版,根据你的PPT呈现出的情绪选用不同的色彩搭配,如果觉得office自带的母版不合适,自己在母版视图中进行调整,自己加背景图、Logo、装饰图等。其实关于母版颜色的选择,这么多年,我也一直没有研究透彻,据说不同的颜色会给人带来不同的感情冲击,专业的书讲的都是些狗屁理论,不就是情绪吗?我就按自己的情绪来定了。当然,如果是有公司自己的标准模版,就不用费这些工夫了,直接用之。

6、在母版视图中调整标题、文字的大小和自体,以及合适的位置。

7、根据母版的色调,将图进行美化,调整颜色、阴影、立体、线条,美化表格、突出文字等。注意在此过程中,把握整个PPT的颜色不要超过3个色系!否则你的PPT就显得特别乱而且“土”!

8、美化页面,看看哪里应该放个装饰图,图片可以从网上找,建议用GOOGLE的图片搜索(用英文最好),装饰图的使用原则是“符合当页主题,大小、颜色不能喧宾夺主!”

9、最后在放映状态下,自己通读一遍,哪里不合适或不满意就调整一下,修改错别字!

10、你以为这就完了吗??没有!注意错别字!(上一步你已改过了,但不够!你自己做自己查的正确率并不高),你知道吃饭的时候,饭里有只苍蝇是什么感觉吗?就是看PPT时看到错别字时的感觉!而且读者一般是老板或客户,会非常怀疑的专业精神和工作态度,前面99%的工作已经做的非常不错了,但你的给读者印象却可能毁于这1%的失误上!因此,将PPT给你的同事或者朋友检查一下,如果文件很重要,建议给2-3个同事检查。

密技真言:

尽量用1种字体,最好不要超过3种
PPT的灵魂—-“逻辑!”
PPT的恶心—-“错别字等于苍蝇”
3色原则:“不要超过3种色系”
6字解码:“大化小,小化图”—-提纲时,用逻辑树尽量将大问题分解成小问题,小问题用图表现。
12字真言:“能用图,不用表;能用表,不用字”
只要掌握如上原则,PPT肯定不会很“糙”或“土”,而且具有专业精神!

02spring IOC基本使用

02spring IOC基本使用

​ 通过前面的介绍我们已经知道了Spring中非常重要的一个特性就是IOC,下面我们将要来看一下如何使用IOC容器,帮助大家更好的体会spring的优势。

1、spring_helloworld

(1)使用手动加载jar包的方式实现,分为三个步骤,现在几乎不用
  • 导包:导入这五个包即可

    commons-logging-1.2.jar
    spring-beans-5.2.3.RELEASE.jar
    spring-context-5.2.3.RELEASE.jar
    spring-core-5.2.3.RELEASE.jar
    spring-expression-5.2.3.RELEASE.jar

  • 写配置

    Person.java

    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
    package com.mashibing.bean;

    public class Person {
    private int id;
    private String name;
    private int age;
    private String gender;

    public int getId() {
    return id;
    }

    public void setId(int id) {
    this.id = id;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public String getGender() {
    return gender;
    }

    public void setGender(String gender) {
    this.gender = gender;
    }

    @Override
    public String toString() {
    return "Person{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", age=" + age +
    ", gender='" + gender + '\'' +
    '}';
    }
    }

    ioc.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--注册一个对象,spring回自动创建这个对象-->
    <!--
    一个bean标签就表示一个对象
    id:这个对象的唯一标识
    class:注册对象的完全限定名
    -->
    <bean id="person" class="com.mashibing.bean.Person">
    <!--使用property标签给对象的属性赋值
    name:表示属性的名称
    value:表示属性的值
    -->
    <property name="id" value="1"></property>
    <property name="name" value="zhangsan"></property>
    <property name="age" value="18"></property>
    <property name="gender" value="男"></property>
    </bean>
    </beans>
  • 测试

SpringDemoTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.mashibing.test;

import com.mashibing.bean.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemoTest {
public static void main(String[] args) {
//ApplicationContext:表示ioc容器
//ClassPathXmlApplicationContext:表示从当前classpath路径中获取xml文件的配置
//根据spring的配置文件来获取ioc容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person person = (Person) context.getBean("person");
System.out.println(person);
}
}
(2)使用maven的方式来构建项目
  • 创建maven项目

    定义项目的groupId、artifactId

  • 添加对应的pom依赖

    pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mashibing</groupId>
    <artifactId>spring_demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.3.RELEASE</version>
    </dependency>
    </dependencies>
    </project>
  • 编写代码

    Person.java

    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
    package com.mashibing.bean;
    public class Person {
    private int id;
    private String name;
    private int age;
    private String gender;

    public int getId() {
    return id;
    }

    public void setId(int id) {
    this.id = id;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public String getGender() {
    return gender;
    }

    public void setGender(String gender) {
    this.gender = gender;
    }

    @Override
    public String toString() {
    return "Person{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", age=" + age +
    ", gender='" + gender + '\'' +
    '}';
    }
    }
  • 测试

    MyTest.java

1
2
3
4
5
6
7
8
9
10
11
import com.mashibing.bean.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person person = (Person) context.getBean("person");
System.out.println(person);
}
}

总结:

​ 以上两种方式创建spring的项目都是可以的,但是在现在的企业开发环境中使用更多的还是maven这样的方式,无须自己处理jar之间的依赖关系,也无须提前下载jar包,只需要配置相关的pom即可,因此推荐大家使用maven的方式,具体的maven操作大家可以看maven的详细操作文档。

搭建spring项目需要注意的点:

​ 1、一定要将配置文件添加到类路径中,使用idea创建项目的时候要放在resource目录下

​ 2、导包的时候别忘了commons-logging-1.2.jar包

细节点:

​ 1、ApplicationContext就是IOC容器的接口,可以通过此对象获取容器中创建的对象

​ 2、对象在Spring容器创建完成的时候就已经创建完成,不是需要用的时候才创建

​ 3、对象在IOC容器中存储的时候都是单例的,如果需要多例需要修改属性

​ 4、创建对象给属性赋值的时候是通过setter方法实现的

​ 5、对象的属性是由setter/getter方法决定的,而不是定义的成员属性

2、spring对象的获取及属性赋值方式

1、通过bean的id获取IOC容器中的对象(上面已经用过)
2、通过bean的类型获取对象

​ MyTest.java

1
2
3
4
5
6
7
8
9
10
11
import com.mashibing.bean.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person bean = context.getBean(Person.class);
System.out.println(bean);
}
}

注意:通过bean的类型在查找对象的时候,在配置文件中不能存在两个类型一致的bean对象,如果有的话,可以通过如下方法

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
import com.mashibing.bean.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person person = context.getBean("person", Person.class);
System.out.println(person);
}
}
3、通过构造器给bean对象赋值

ioc.xml

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
<!--给person类添加构造方法-->
<bean id="person2" class="com.mashibing.bean.Person">
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="name" value="lisi"></constructor-arg>
<constructor-arg name="age" value="20"></constructor-arg>
<constructor-arg name="gender" value="女"></constructor-arg>
</bean>

<!--在使用构造器赋值的时候可以省略name属性,但是此时就要求必须严格按照构造器参数的顺序来填写了-->
<bean id="person3" class="com.mashibing.bean.Person">
<constructor-arg value="1"></constructor-arg>
<constructor-arg value="lisi"></constructor-arg>
<constructor-arg value="20"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</bean>

<!--如果想不按照顺序来添加参数值,那么可以搭配index属性来使用-->
<bean id="person4" class="com.mashibing.bean.Person">
<constructor-arg value="lisi" index="1"></constructor-arg>
<constructor-arg value="1" index="0"></constructor-arg>
<constructor-arg value="女" index="3"></constructor-arg>
<constructor-arg value="20" index="2"></constructor-arg>
</bean>
<!--当有多个参数个数相同,不同类型的构造器的时候,可以通过type来强制类型-->
将person的age类型设置为Integer类型
public Person(int id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
System.out.println("Age");
}

public Person(int id, String name, String gender) {
this.id = id;
this.name = name;
this.gender = gender;
System.out.println("gender");
}
<bean id="person5" class="com.mashibing.bean.Person">
<constructor-arg value="1"></constructor-arg>
<constructor-arg value="lisi"></constructor-arg>
<constructor-arg value="20" type="java.lang.Integer"></constructor-arg>
</bean>
<!--如果不修改为integer类型,那么需要type跟index组合使用-->
<bean id="person5" class="com.mashibing.bean.Person">
<constructor-arg value="1"></constructor-arg>
<constructor-arg value="lisi"></constructor-arg>
<constructor-arg value="20" type="int" index="2"></constructor-arg>
</bean>
4、通过命名空间为bean赋值,简化配置文件中属性声明的写法

​ 1、导入命名空间

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

​ 2、添加配置

1
<bean id="person6" class="com.mashibing.bean.Person" p:id="3" p:name="wangwu" p:age="22" p:gender="男"></bean>
5、为复杂类型进行赋值操作

​ 在之前的测试代码中,我们都是给最基本的属性进行赋值操作,在正常的企业级开发中还会遇到给各种复杂类型赋值,如集合、数组、其他对象等。

​ Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package com.mashibing.bean;

import java.util.*;

public class Person {
private int id;
private String name="dahuang";
private int age;
private String gender;
private Address address;
private String[] hobbies;
private List<Book> books;
private Set<Integer> sets;
private Map<String,Object> maps;
private Properties properties;

public Person(int id, String name, int age, String gender) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
System.out.println("有参构造器");
}

public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
System.out.println("Age");
}

public Person(int id, String name, String gender) {
this.id = id;
this.name = name;
this.gender = gender;
System.out.println("gender");
}

public Person() {
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

public List<Book> getBooks() {
return books;
}

public void setBooks(List<Book> books) {
this.books = books;
}

public Map<String, Object> getMaps() {
return maps;
}

public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}

public Properties getProperties() {
return properties;
}

public void setProperties(Properties properties) {
this.properties = properties;
}

public String[] getHobbies() {
return hobbies;
}

public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}

public Set<Integer> getSets() {
return sets;
}

public void setSets(Set<Integer> sets) {
this.sets = sets;
}

@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", address=" + address +
", hobbies=" + Arrays.toString(hobbies) +
", books=" + books +
", sets=" + sets +
", maps=" + maps +
", properties=" + properties +
'}';
}
}

Book.java

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
package com.mashibing.bean;

public class Book {
private String name;
private String author;
private double price;

public Book() {
}

public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}

Address.java

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
package com.mashibing.bean;

public class Address {
private String province;
private String city;
private String town;

public Address() {
}

public Address(String province, String city, String town) {
this.province = province;
this.city = city;
this.town = town;
}

public String getProvince() {
return province;
}

public void setProvince(String province) {
this.province = province;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getTown() {
return town;
}

public void setTown(String town) {
this.town = town;
}

@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
", town='" + town + '\'' +
'}';
}
}

ioc.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"
>

<!--给复杂类型的赋值都在property标签内进行-->
<bean id="person" class="com.mashibing.bean.Person">
<property name="name">
<!--赋空值-->
<null></null>
</property>
<!--通过ref引用其他对象,引用外部bean-->
<property name="address" ref="address"></property>
<!--引用内部bean-->
<!-- <property name="address">
<bean class="com.mashibing.bean.Address">
<property name="province" value="北京"></property>
<property name="city" value="北京"></property>
<property name="town" value="西城区"></property>
</bean>
</property>-->
<!--为list赋值-->
<property name="books">
<list>
<!--内部bean-->
<bean id="book1" class="com.mashibing.bean.Book">
<property name="name" value="多线程与高并发"></property>
<property name="author" value="马士兵"></property>
<property name="price" value="1000"></property>
</bean>
<!--外部bean-->
<ref bean="book2"></ref>
</list>
</property>
<!--给map赋值-->
<property name="maps" ref="myMap"></property>
<!--给property赋值-->
<property name="properties">
<props>
<prop key="aaa">aaa</prop>
<prop key="bbb">222</prop>
</props>
</property>
<!--给数组赋值-->
<property name="hobbies">
<array>
<value>book</value>
<value>movie</value>
<value>game</value>
</array>
</property>
<!--给set赋值-->
<property name="sets">
<set>
<value>111</value>
<value>222</value>
<value>222</value>
</set>
</property>
</bean>
<bean id="address" class="com.mashibing.bean.Address">
<property name="province" value="河北"></property>
<property name="city" value="邯郸"></property>
<property name="town" value="武安"></property>
</bean>
<bean id="book2" class="com.mashibing.bean.Book">
<property name="name" value="JVM"></property>
<property name="author" value="马士兵"></property>
<property name="price" value="1200"></property>
</bean>
<!--级联属性-->
<bean id="person2" class="com.mashibing.bean.Person">
<property name="address" ref="address"></property>
<property name="address.province" value="北京"></property>
</bean>
<!--util名称空间创建集合类型的bean-->
<util:map id="myMap">
<entry key="key1" value="value1"></entry>
<entry key="key2" value-ref="book2"></entry>
<entry key="key03">
<bean class="com.mashibing.bean.Book">
<property name="name" value="西游记" ></property>
<property name="author" value="吴承恩" ></property>
<property name="price" value="100" ></property>
</bean>
</entry>
</util:map>
</beans>
6、继承关系bean的配置

ioc.xml

1
2
3
4
5
6
7
8
9
10
<bean id="person" class="com.mashibing.bean.Person">
<property name="id" value="1"></property>
<property name="name" value="zhangsan"></property>
<property name="age" value="21"></property>
<property name="gender" value="男"></property>
</bean>
<!--parent:指定bean的配置信息继承于哪个bean-->
<bean id="person2" class="com.mashibing.bean.Person" parent="person">
<property name="name" value="lisi"></property>
</bean>

如果想实现Java文件的抽象类,不需要将当前bean实例化的话,可以使用abstract属性

1
2
3
4
5
6
7
8
9
10
<bean id="person" class="com.mashibing.bean.Person" abstract="true">
<property name="id" value="1"></property>
<property name="name" value="zhangsan"></property>
<property name="age" value="21"></property>
<property name="gender" value="男"></property>
</bean>
<!--parent:指定bean的配置信息继承于哪个bean-->
<bean id="person2" class="com.mashibing.bean.Person" parent="person">
<property name="name" value="lisi"></property>
</bean>
7、bean对象创建的依赖关系

​ bean对象在创建的时候是按照bean在配置文件的顺序决定的,也可以使用depend-on标签来决定顺序

ioc.xml

1
2
3
<bean id="book" class="com.mashibing.bean.Book" depends-on="person,address"></bean>
<bean id="address" class="com.mashibing.bean.Address"></bean>
<bean id="person" class="com.mashibing.bean.Person"></bean>
8、bean的作用域控制,是否是单例

ioc.xml

1
2
3
4
5
6
7
8
9
10
11
<!--
bean的作用域:singleton、prototype、request、session
默认情况下是单例的
prototype:多实例的
容器启动的时候不会创建多实例bean,只有在获取对象的时候才会创建该对象
每次创建都是一个新的对象
singleton:默认的单例对象
在容器启动完成之前就已经创建好对象
获取的所有对象都是同一个
-->
<bean id="person4" class="com.mashibing.bean.Person" scope="prototype"></bean>
9、利用工厂模式创建bean对象

​ 在之前的案例中,所有bean对象的创建都是通过反射得到对应的bean实例,其实在spring中还包含另外一种创建bean实例的方式,就是通过工厂模式进行对象的创建

​ 在利用工厂模式创建bean实例的时候有两种方式,分别是静态工厂和实例工厂。

​ 静态工厂:工厂本身不需要创建对象,但是可以通过静态方法调用,对象=工厂类.静态工厂方法名();

​ 实例工厂:工厂本身需要创建对象,工厂类 工厂对象=new 工厂类;工厂对象.get对象名();

PersonStaticFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.mashibing.factory;

import com.mashibing.bean.Person;

public class PersonStaticFactory {

public static Person getPerson(String name){
Person person = new Person();
person.setId(1);
person.setName(name);
return person;
}
}

ioc.xml

1
2
3
4
5
6
7
8
9
<!--
静态工厂的使用:
class:指定静态工厂类
factory-method:指定哪个方法是工厂方法
-->
<bean id="person5" class="com.mashibing.factory.PersonStaticFactory" factory-method="getPerson">
<!--constructor-arg:可以为方法指定参数-->
<constructor-arg value="lisi"></constructor-arg>
</bean>

PersonInstanceFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.mashibing.factory;

import com.mashibing.bean.Person;

public class PersonInstanceFactory {
public Person getPerson(String name){
Person person = new Person();
person.setId(1);
person.setName(name);
return person;
}
}

ioc.xml

1
2
3
4
5
6
7
8
9
10
<!--实例工厂使用-->
<!--创建实例工厂类-->
<bean id="personInstanceFactory" class="com.mashibing.factory.PersonInstanceFactory"></bean>
<!--
factory-bean:指定使用哪个工厂实例
factory-method:指定使用哪个工厂实例的方法
-->
<bean id="person6" class="com.mashibing.bean.Person" factory-bean="personInstanceFactory" factory-method="getPerson">
<constructor-arg value="wangwu"></constructor-arg>
</bean>
10、继承FactoryBean来创建对象

​ FactoryBean是Spring规定的一个接口,当前接口的实现类,Spring都会将其作为一个工厂,但是在ioc容器启动的时候不会创建实例,只有在使用的时候才会创建对象

MyFactoryBean.java

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
package com.mashibing.factory;

import com.mashibing.bean.Person;
import org.springframework.beans.factory.FactoryBean;

/**
* 实现了FactoryBean接口的类是Spring中可以识别的工厂类,spring会自动调用工厂方法创建实例
*/
public class MyFactoryBean implements FactoryBean<Person> {

/**
* 工厂方法,返回需要创建的对象
* @return
* @throws Exception
*/
@Override
public Person getObject() throws Exception {
Person person = new Person();
person.setName("maliu");
return person;
}

/**
* 返回创建对象的类型,spring会自动调用该方法返回对象的类型
* @return
*/
@Override
public Class<?> getObjectType() {
return Person.class;
}

/**
* 创建的对象是否是单例对象
* @return
*/
@Override
public boolean isSingleton() {
return false;
}
}

ioc.xml

1
<bean id="myfactorybean" class="com.mashibing.factory.MyFactoryBean"></bean>
11、bean对象的初始化和销毁方法

​ 在创建对象的时候,我们可以根据需要调用初始化和销毁的方法

Address.java

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
package com.mashibing.bean;

public class Address {
private String province;
private String city;
private String town;

public Address() {
System.out.println("address被创建了");
}

public Address(String province, String city, String town) {
this.province = province;
this.city = city;
this.town = town;
}

public String getProvince() {
return province;
}

public void setProvince(String province) {
this.province = province;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getTown() {
return town;
}

public void setTown(String town) {
this.town = town;
}

public void init(){
System.out.println("对象被初始化");
}

public void destory(){
System.out.println("对象被销毁");
}

@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
", town='" + town + '\'' +
'}';
}
}

ioc.xml

1
2
3
4
5
<!--bean生命周期表示bean的创建到销毁
如果bean是单例,容器在启动的时候会创建好,关闭的时候会销毁创建的bean
如果bean是多礼,获取的时候创建对象,销毁的时候不会有任何的调用
-->
<bean id="address" class="com.mashibing.bean.Address" init-method="init" destroy-method="destory"></bean>

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.mashibing.bean.Address;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc2.xml");
Address address = context.getBean("address", Address.class);
System.out.println(address);
//applicationContext没有close方法,需要使用具体的子类
((ClassPathXmlApplicationContext)context).close();

}
}
12、配置bean对象初始化方法的前后处理方法

​ spring中包含一个BeanPostProcessor的接口,可以在bean的初始化方法的前后调用该方法,如果配置了初始化方法的前置和后置处理器,无论是否包含初始化方法,都会进行调用

MyBeanPostProcessor.java

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
package com.mashibing.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在初始化方法调用之前执行
* @param bean 初始化的bean对象
* @param beanName xml配置文件中的bean的id属性
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization:"+beanName+"调用初始化前置方法");
return bean;
}

/**
* 在初始化方法调用之后执行
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization:"+beanName+"调用初始化后缀方法");
return bean;
}
}

ioc.xml

1
<bean id="myBeanPostProcessor" class="com.mashibing.bean.MyBeanPostProcessor"></bean>

3、spring创建第三方bean对象

​ 在Spring中,很多对象都是单实例的,在日常的开发中,我们经常需要使用某些外部的单实例对象,例如数据库连接池,下面我们来讲解下如何在spring中创建第三方bean实例。

​ 1、导入数据库连接池的pom文件

1
2
3
4
5
6
7
8
9
10
11
12
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>

​ 2、编写配置文件

ioc.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
</beans>

​ 3、编写测试文件

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.alibaba.druid.pool.DruidDataSource;
import com.mashibing.bean.Address;
import com.mashibing.bean.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc3.xml");
DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
System.out.println(dataSource);
System.out.println(dataSource.getConnection());
}
}

4、spring引用外部配置文件

在resource中添加dbconfig.properties

1
2
3
4
username=root
password=123456
url=jdbc:mysql://localhost:3306/demo
driverClassName=com.mysql.jdbc.Driver

编写配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--加载外部配置文件
在加载外部依赖文件的时候需要context命名空间
-->
<context:property-placeholder location="classpath:dbconfig.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
<property name="url" value="${url}"></property>
<property name="driverClassName" value="${driverClassName}"></property>
</bean>
</beans>

5、spring基于xml文件的自动装配

​ 当一个对象中需要引用另外一个对象的时候,在之前的配置中我们都是通过property标签来进行手动配置的,其实在spring中还提供了一个非常强大的功能就是自动装配,可以按照我们指定的规则进行配置,配置的方式有以下几种:

​ default/no:不自动装配

​ byName:按照名字进行装配,以属性名作为id去容器中查找组件,进行赋值,如果找不到则装配null

​ byType:按照类型进行装配,以属性的类型作为查找依据去容器中找到这个组件,如果有多个类型相同的bean对象,那么会报异常,如果找不到则装配null

​ constructor:按照构造器进行装配,先按照有参构造器参数的类型进行装配,没有就直接装配null;如果按照类型找到了多个,那么就使用参数名作为id继续匹配,找到就装配,找不到就装配null

ioc.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="address" class="com.mashibing.bean.Address">
<property name="province" value="河北"></property>
<property name="city" value="邯郸"></property>
<property name="town" value="武安"></property>
</bean>
<bean id="person" class="com.mashibing.bean.Person" autowire="byName"></bean>
<bean id="person2" class="com.mashibing.bean.Person" autowire="byType"></bean>
<bean id="person3" class="com.mashibing.bean.Person" autowire="constructor"></bean>
</beans>

6、SpEL的使用

​ SpEL:Spring Expression Language,spring的表达式语言,支持运行时查询操作对象

​ 使用#{…}作为语法规则,所有的大括号中的字符都认为是SpEL.

ioc.xml

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="person4" class="com.mashibing.bean.Person">
<!--支持任何运算符-->
<property name="age" value="#{12*2}"></property>
<!--可以引用其他bean的某个属性值-->
<property name="name" value="#{address.province}"></property>
<!--引用其他bean-->
<property name="address" value="#{address}"></property>
<!--调用静态方法-->
<property name="hobbies" value="#{T(java.util.UUID).randomUUID().toString().substring(0,4)}"></property>
<!--调用非静态方法-->
<property name="gender" value="#{address.getCity()}"></property>
</bean>

01spring初识

spring初识

1、框架

​ 框架就是一些类和接口的集合,通过这些类和接口协调来完成一系列的程序实现。JAVA框架可以分为三层:表示层,业务层和物理层。框架又叫做开发中的半成品,它不能提供整个WEB应用程序的所有东西,但是有了框架,我们就可以集中精力进行业务逻辑的开发而不用去关心它的技术实现以及一些辅助的业务逻辑。大家熟知的Structs和Spring就是表示层和业务层框架的强力代表。(说的太官方了)

​ 人话:

​ 框架就是某些个人或者组织定义了一系列的类或者接口,提前定义好了一些实现,用户可以在这些类和接口的基础之上,使用这些类来迅速的形成某个领域或者某个行业的解决方案,简化开发的过程,提高开发的效率。就好比:你要盖一座房子,先把柱子,房梁等先建设好,然后只需要向房子中填充就可以了,可以按照自己的需求进行设计,其实我们做的项目、系统都是类似的方式,如果所有的代码全部都需要自己实现,那么这个工程就太庞大了,所以可以先创建出一些基础的模板框架,开发人员只需要按照自己的需求向架子中填充内容,完成自己独特需求即可,这就是框架存在的意义。其实我们之前定义的简单的工具类这些东西也是类似的原理,只不过比较单一简单而已,因此,在现在的很多项目系统开发的过程中都是利用框架进行开发。

2、spring(春天)

架构设计

​ 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

image

​ 单一应用架构

​ 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

​ 垂直应用架构

​ 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

​ 分布式服务架构

​ 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

​ 流动计算架构

​ 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

Java主流框架演变之路

​ 1、JSP+Servlet+JavaBean

​ 2、MVC三层架构

mvc

​ 3、使用EJB进行应用的开发,但是EJB是重量级框架(在使用的时候,过多的接口和依赖,侵入性强),在使用上比较麻烦

​ 4、Struts1/Struts2+Hibernate+Spring

​ 5、SpringMVC+Mybatis+Spring

​ 6、SpringBoot开发,约定大于配置

Spring官网

官网地址:https://spring.io/projects/spring-framework#overview

压缩包下载地址:https://repo.spring.io/release/org/springframework/spring/

源码地址:https://github.com/spring-projects/spring-framework

1
2
3
4
5
6
7
8
9
10
11
Spring makes it easy to create Java enterprise applications. It provides everything you need to embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as alternative languages on the JVM, and with the flexibility to create many kinds of architectures depending on an application’s needs. As of Spring Framework 5.1, Spring requires JDK 8+ (Java SE 8+) and provides out-of-the-box support for JDK 11 LTS. Java SE 8 update 60 is suggested as the minimum patch release for Java 8, but it is generally recommended to use a recent patch release.

Spring supports a wide range of application scenarios. In a large enterprise, applications often exist for a long time and have to run on a JDK and application server whose upgrade cycle is beyond developer control. Others may run as a single jar with the server embedded, possibly in a cloud environment. Yet others may be standalone applications (such as batch or integration workloads) that do not need a server.

Spring is open source. It has a large and active community that provides continuous feedback based on a diverse range of real-world use cases. This has helped Spring to successfully evolve over a very long time.

Spring 使创建 Java 企业应用程序变得更加容易。它提供了在企业环境中接受 Java 语言所需的一切,,并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并可根据应用程序的需要灵活地创建多种体系结构。 从 Spring Framework 5.0 开始,Spring 需要 JDK 8(Java SE 8+),并且已经为 JDK 9 提供了现成的支持。

Spring支持各种应用场景, 在大型企业中, 应用程序通常需要运行很长时间,而且必须运行在 jdk 和应用服务器上,这种场景开发人员无法控制其升级周期。 其他可能作为一个单独的jar嵌入到服务器去运行,也有可能在云环境中。还有一些可能是不需要服务器的独立应用程序(如批处理或集成的工作任务)。

Spring 是开源的。它拥有一个庞大而且活跃的社区,提供不同范围的,真实用户的持续反馈。这也帮助Spring不断地改进,不断发展。

核心解释

​ spring是一个开源框架。

​ spring是为了简化企业开发而生的,使得开发变得更加优雅和简洁。

​ spring是一个IOCAOP的容器框架。

​ IOC:控制反转

​ AOP:面向切面编程

​ 容器:包含并管理应用对象的生命周期,就好比用桶装水一样,spring就是桶,而对象就是水

使用spring的优点

​ 1、Spring通过DI、AOP和消除样板式代码来简化企业级Java开发

​ 2、Spring框架之外还存在一个构建在核心框架之上的庞大生态圈,它将Spring扩展到不同的领域,如Web服务、REST、移动开发以及NoSQL

​ 3、低侵入式设计,代码的污染极低

​ 4、独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺

​ 5、Spring的IoC容器降低了业务对象替换的复杂性,提高了组件之间的解耦

​ 6、Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用

​ 7、Spring的ORM和DAO提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问

​ 8、Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部

如何简化开发

​ 基于POJO的轻量级和最小侵入性编程

​ 通过依赖注入和面向接口实现松耦合

​ 基于切面和惯例进行声明式编程

​ 通过切面和模板减少样板式代码

spring的模块划分图

overview

1
2
3
4
5
6
7
8
模块解释:
Test:Spring的单元测试模块
Core Container:核心容器模块
AOP+Aspects:面向切面编程模块
Instrumentation:提供了class instrumentation支持和类加载器的实现来在特定的应用服务器上使用,几乎不用
Messaging:包括一系列的用来映射消息到方法的注解,几乎不用
Data Access/Integration:数据的获取/整合模块,包括了JDBC,ORM,OXM,JMS和事务模块
Web:提供面向web整合特性

3、IOC(Inversion of Control):控制反转

为什么要引入IOC

创建一个普通的java项目,完成下述功能

UserDao.java

1
2
3
4
5
package com.mashibing.dao;

public interface UserDao {
public void getUser();
}

UserDaoImpl.java

1
2
3
4
5
6
7
8
9
10
package com.mashibing.dao.impl;

import com.mashibing.dao.UserDao;

public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("获取用户数据");
}
}

UserService.java

1
2
3
4
5
package com.mashibing.service;

public interface UserService {
public void getUser();
}

UserServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.mashibing.service.impl;

import com.mashibing.dao.UserDao;
import com.mashibing.dao.impl.UserDaoImpl;
import com.mashibing.dao.impl.UserDaoMysqlImpl;
import com.mashibing.service.UserService;

public class UserServiceImpl implements UserService {

private UserDao userDao = new UserDaoImpl();

@Override
public void getUser() {
userDao.getUser();
}
}

SpringDemoTest.java

1
2
3
4
5
6
7
8
9
10
11
package com.mashibing.test;

import com.mashibing.service.UserService;
import com.mashibing.service.impl.UserServiceImpl;

public class SpringDemoTest {
public static void main(String[] args) {
UserService service = new UserServiceImpl();
service.getUser();
}
}

在之前的代码编写过程中,我们都是这么完成我们的功能的,但是如果增加一个UserDao的实现类呢?

UserDaoMysqlImpl.java

1
2
3
4
5
6
7
8
9
10
package com.mashibing.dao.impl;

import com.mashibing.dao.UserDao;

public class UserDaoMysqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("mysql");
}
}

如果我们想要使用mysql的话,那么就必须要修改UserServiceImpl.java的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.mashibing.service.impl;

import com.mashibing.dao.UserDao;
import com.mashibing.dao.impl.UserDaoImpl;
import com.mashibing.dao.impl.UserDaoMysqlImpl;
import com.mashibing.service.UserService;

public class UserServiceImpl implements UserService {

private UserDao userDao = new UserDaoImpl();

@Override
public void getUser() {
userDao.getUser();
}
}

但是如果我们再增加一个oracle的类呢?

UserDaoOracleImpl.java

1
2
3
4
5
6
7
8
9
10
package com.mashibing.dao.impl;

import com.mashibing.dao.UserDao;

public class UserDaoOracleImpl implements UserDao {
@Override
public void getUser() {
System.out.println("oracle");
}
}

此时UserService还是要继续修改,很显然这样的方式已经不适用于我们的需求了,那么怎么解决呢,可以使用如下的方式

UserServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.mashibing.service.impl;

import com.mashibing.dao.UserDao;
import com.mashibing.dao.impl.UserDaoImpl;
import com.mashibing.service.UserService;

public class UserServiceImpl implements UserService {
private UserDao userDao;

public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}

测试类SpringDemoTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.mashibing.test;

import com.mashibing.dao.impl.UserDaoMysqlImpl;
import com.mashibing.dao.impl.UserDaoOracleImpl;
import com.mashibing.service.UserService;
import com.mashibing.service.impl.UserServiceImpl;

public class SpringDemoTest {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserDaoMysqlImpl());
userService.getUser();

userService.setUserDao(new UserDaoOracleImpl());
userService.getUser();
}
}

其实从刚刚的代码中,大家应该能体会解耦的重要性了,下面我们就开始学习Spring的IOC。

IOC初始

​ 想要搞明白IOC,那么需要搞清楚如下几个问题:

1
2
3
4
1、谁控制谁
2、控制什么
3、什么是反转
4、哪些方面被反转

基本概念

1
2
IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.
IOC与大家熟知的依赖注入同理,. 这是一个通过依赖注入对象的过程 也就是说,它们所使用的对象,是通过构造函数参数,工厂方法的参数或这是从工厂方法的构造函数或返回值的对象实例设置的属性,然后容器在创建bean时注入这些需要的依赖。 这个过程相对普通创建对象的过程是反向的(因此称之为IoC),bean本身通过直接构造类来控制依赖关系的实例化或位置,或提供诸如服务定位器模式之类的机制。

如果这个过程比较难理解的话,那么可以想象自己找女朋友和婚介公司找女朋友的过程。如果这个过程能够想明白的话,那么我们现在回答上面的问题:

1
2
3
4
1、谁控制谁:在之前的编码过程中,都是需要什么对象自己去创建什么对象,有程序员自己来控制对象,而有了IOC容器之后,就会变成由IOC容器来控制对象,
2、控制什么:在实现过程中所需要的对象及需要依赖的对象
3、什么是反转:在没有IOC容器之前我们都是在对象中主动去创建依赖的对象,这是正转的,而有了IOC之后,依赖的对象直接由IOC容器创建后注入到对象中,由主动创建变成了被动接受,这是反转
4、哪些方面被反转:依赖的对象

DI与IOC

1
很多人把IOC和DI说成一个东西,笼统来说的话是没有问题的,但是本质上还是有所区别的,希望大家能够严谨一点,IOC和DI是从不同的角度描述的同一件事,IOC是从容器的角度描述,而DI是从应用程序的角度来描述,也可以这样说,IOC是设计思想,而DI是具体的实现方式

4、总结

​ 在此处总结中,希望大家能够能够明白两件事:

1、解耦

​ 在面向对象设计的软件系统中,底层的实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑。

​ 需要注意的是,在这样的组合关系中,一旦某一个对象出现了问题,那么其他对象肯定回有所影响,这就是耦合性太高的缘故,但是对象的耦合关系是无法避免的,也是必要的。随着应用程序越来越庞大,对象的耦合关系可能越来越复杂,经常需要多重依赖关系,因此,无论是架构师还是程序员,在面临这样的场景的时候,都需要减少这些对象的耦合性。

​ 耦合的关系不仅仅是对象与对象之间,也会出现在软件系统的各个模块之间,是我们需要重点解决的问题。而为了解决对象之间的耦合度过高的问题,我们就可以通过IOC来实现对象之间的解耦,spring框架就是IOC理论最最广泛的应用。

​ 从上图中可以看到,当引入了第三方的容器之后,几个对象之间就没有了耦合关系,全部对象都交由容器来控制,这个容器就相当于粘合剂,将系统的对象粘合在一起发挥作用。

2、生态

​ 任何一个语言或者任何一个框架想要立于不败之地,那么很重要的就是它的生态。

多线程starter

1.什么是线程

基本概念

我们先从线程的基本概念开始,给大家复习一下,不知道有多少同学是基础不太好,说什么是线程都不知道的,如果这样的话,花时间去补初级内容的课。

什么是叫一个进程? 什么叫一个线程?

  • Program app ->QQ.exe

    进程:做一个简单的解释,你的硬盘上有一个简单的程序,这个程序叫QQ.exe,这是一个程序,这个程序是一个静态的概念,它被扔在硬盘上也没人理他,但是当你双击它,弹出一个界面输入账号密码登录进去了,OK,这个时候叫做一个进程。进程相对于程序来说它是一个动态的概念

    线程:作为一个进程里面最小的执行单元它就叫一个线程,用简单的话讲一个程序里不同的执行路径就叫做一个线程

示例:什么叫做线程

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
package com.mashibing.juc.c_000;

import java.util.concurrent.TimeUnit;

public class T01_WhatIsThread {
private static class T1 extends Thread {
@Override
public void run() {
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1");
}
}
}

public static void main(String[] args) {
//new T1().run();
new T1().start();
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main");
}

}
}

观察上面程序的数据结果,你会看到字符串“T1”和“Main”的交替输出,这就是程序中有两条不同的执行路径在交叉执行,这就是直观概念上的线程,概念性的东西,理解就好,没有必要咬文嚼字的去背文字的定义。

2.线程常用方法

我们来认识几个线程的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.mashibing.juc.c_000;

public class T03_Sleep_Yield_Join {
public static void main(String[] args) {
//testSleep();
//testYield();
testJoin();
}
/*Sleep,意思就是睡眠,当前线程暂停一段时间让给别的线程去运行。Sleep是怎么复活的?由你的睡眠时间而定,等睡眠到规定的时间自动复活*/
static void testSleep() {
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
/*Yield,就是当前线程正在执行的时候停止下来进入等待队列,回到等待队列里在系统的调度算法里头呢还是依然有可能把你刚回去的这个线程拿回来继续执行,当然,更大的可能性是把原来等待的那些拿出一个来执行,所以yield的意思是我让出一下CPU,后面你们能不能抢到那我不管*/
static void testYield() {
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
if(i%10 == 0) Thread.yield();


}
}).start();

new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("------------B" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
}
/*join, 意思就是在自己当前线程加入你调用Join的线程(),本线程等待。等调用的线程运行完了,自己再去执行。t1和t2两个线程,在t1的某个点上调用了t2.join,它会跑到t2去运行,t1等待t2运行完毕继续t1运行(自己join自己没有意义) */
static void testJoin() {
Thread t1 = new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

Thread t2 = new Thread(()->{

try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

t1.start();
t2.start();
}
}

3.启动线程的五种方式

创建线程的几种方式

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
package com.mashibing.juc.c_000;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class T02_HowToCreateThread {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}

static class MyRun implements Runnable {
@Override
public void run() {
System.out.println("Hello MyRun!");
}
}

static class MyCall implements Callable<String> {
@Override
public String call() {
System.out.println("Hello MyCall");
return "success";
}
}

//启动线程的5种方式
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRun()).start();
new Thread(()->{
System.out.println("Hello Lambda!");
}).start();

Thread t = new Thread(new FutureTask<String>(new MyCall()));
t.start();

ExecutorService service = Executors.newCachedThreadPool();
service.execute(()->{
System.out.println("Hello ThreadPool");
});
service.shutdown();
}

}

分享一道面试题

请你告诉我启动线程的三种方式 ?
你说第一个:new Thread().start(); 第二个: new Thread(Runnable).start() 这没问题 ;那第三个呢,要回答线程池也是用的这两种之一,他这么问有些吹毛求疵的意思,你就可以说通过线程池也可以启动一个新的线程 3:Executors.newCachedThreadPool()或者FutureTask + Callable

4.线程同步的基本概念

synchronized

下面我们来讲synchronized关键字,有不少同学已经耳熟能详了,不过作为复习还是要复习一下。第一个是多个线程去访问同一个资源的时候对这个资源上锁。

为什么要上锁呢?访问某一段代码或者某临界资源的时候是需要有一把锁的概念在这儿的。

比如:我们对一个数字做递增,两个程序对它一块儿来做递增,递增就是把一个程序往上加1啊,如果两个线程共同访问的时候,第一个线程一读它是0,然后把它加1,在自己线程内部内存里面算还没有写回去的时候而第二个线程读到了它还是0,加1在写回去,本来加了两次,但还是1,那么我们在对这个数字递增的过程当中就上把锁,就是说第一个线程对这个数字访问的时候是独占的,不允许别的线程来访问,不允许别的线程来对它计算,我必须加完1收释放锁,其他线程才能对它继续加。

实质上,这把锁并不是对数字进行锁定的, 你可以任意指定,想锁谁就锁谁。

我第一个小程序是这么写的 ,如果说你想上了把锁之后才能对count进行减减访问,你可以new一个Object,所以这里锁定就是o,当我拿到这把锁的时候才能执行这段代码。是锁定的某一个对象,synchronized有一个锁升级的概念,我们一会儿会讲到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
*synchronized关键字
*对某个对象加锁
*@author mashibing
*/
package com.mashibing.juc.c_001;

public class T {

private int count = 10;
private Object o = new Object();

public void m() {
synchronized(o) { //任何线程要想执行下面的代码,必须先拿到o的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}

}

我们来谈一下synchronized它的一些特性。如果说你每次都定义个一个锁的对象Object o 把它new出来那加锁的时候太麻烦每次都要new一个新的对象出来,所以呢,有一个简单的方式就是synchronized(this)锁定当前对象就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* synchronized关键字
* 对某个对象加锁
* @author mashibing
*/
package com.mashibing.juc.c_002;

public class T {

private int count = 10;

public void m() {
synchronized(this) { ߳//任何线程想要执行那个下面的代码,必须先要拿到this的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}

}

如果你要是锁定当前对象呢,你也可以写成如下方法。synchronized方法和synchronized(this)执行这段代码它是等值的

1
2
3
4
5
6
7
8
9
10
11
package com.mashibing.juc.c_003;

public class T {

private int count = 10;

public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}

我们知道静态方法static是没有this对象的,你不需要new出一个对象来就能执行这个方法,但如果这个这个上面加一个synchronized的话就代表synchronized(T.class)。这里这个synchronized(T.class)锁的就是T类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.mashibing.juc.c_004;

public class T {

private static int count = 10;

public synchronized static void m() { //这里等同于synchronized(T.class)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}

public static void mm() {
synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?
count --;
}
}
}

问题:T.class是单例的吗?

一个class load到内存它是不是单例的,想想看。一般情况下是,如果是在同一个ClassLoader空间那它一定是。不是同一个类加载器就不是了,不同的类加载器互相之间也不能访问。所以说你能访问它,那他一定就是单例

下面程序:很有可能读不到别的线程修改过的内容,除了这点之外count减减完了之后下面的count输出和你减完的结果不对,很容易分析:如果有一个线程把它从10减到9了,然后又有一个线程在前面一个线程还没有输出呢进来了把9又减到了8,继续输出的8,而不是9。如果你想修正它,前面第一个是在上面加volatile,改了马上就能得到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 分析一下这个程序的输出
* @author mashibing
*/
package com.mashibing.juc.c_005;

public class T implements Runnable {

private /*volatile*/ int count = 100;

public /*synchronized*/ void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}

public static void main(String[] args) {
T t = new T();
for(int i=0; i<100; i++) {
new Thread(t, "THREAD" + i).start();
}
}

}

另外这个之外还可以加synchronized,加了synchronized就没有必要在加volatile了,因为synchronized既保证了原子性,又保证了可见性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//对比上一个小程序
package com.mashibing.juc.c_006;
public class T implements Runnable {

private int count = 10;

public synchronized void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}

public static void main(String[] args) {

for(int i=0; i<5; i++) {
T t = new T();
new Thread(t, "THREAD" + i).start();
}
}

}

如下代码:同步方法和非同步方法是否可以同时调用?就是我有一个synchronized的m1方法,我调用m1的时候能不能调用m2,拿大腿想一想这个是肯定可以的,线程里面访问m1的时候需要加锁,可是访问m2的时候我又不需要加锁,所以允许执行m2。

这些小实验的设计是比较考验功力的,学习线程的时候自己要多动手进行试验,任何一个理论,都可以进行验证。

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
/**
*同步和非同步方法是否可以同时调用?
* @author mashibing
*/
package com.mashibing.juc.c_007;
public class T {

public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}

public void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2 ");
}

public static void main(String[] args) {
T t = new T();

/*new Thread(()->t.m1(), "t1").start();
new Thread(()->t.m2(), "t2").start();*/

new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();

/*
//1.8之前的写法
new Thread(new Runnable() {

@Override
public void run() {
t.m1();
}
});
*/
}
}

我们在来看一个synchronized应用的例子

我们定义了一个class账户,有名称、余额。写方法给哪个用户设置它多少余额,读方法通过这个名字得到余额值。如果我们给写方法加锁,给读方法不加锁,你的业务允许产生这种问题吗?业务说我中间读到了一些不太好的数据也没关系,如果不允许客户读到中间不好的数据那这个就有问题。正因为我们加了锁的方法和不加锁的方法可以同时运行。

问题比如说:张三,给他设置100块钱启动了,睡了1毫秒之后呢去读它的值,然后再睡2秒再去读它的值这个时候你会看到读到的值有问题,原因是在设定的过程中this.name你中间睡了一下,这个过程当中我模拟了一个线程来读,这个时候调用的是getBalance方法,而调用这个方法的时候是不用加锁的,所以说我不需要等你整个过程执行完就可以读到你中间结果产生的内存,这个现象就叫做脏读。这问题的产生就是synchronized方法和非synchronized方法是同时运行的。解决就是把getBalance加上synchronized就可以了,如果你的业务允许脏读,就可以不用加锁,加锁之后的效率低下。

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
/**
* 面试题:模拟银行账户
* 对业务写方法加锁
* 对业务读方法不加锁
* 这样行不行?
*
* 容易产生脏读问题(dirtyRead)
*/
package com.mashibing.juc.c_008;

import java.util.concurrent.TimeUnit;

public class Account {
String name;
double balance;

public synchronized void set(String name, double balance) {
this.name = name;

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

this.balance = balance;
}

public /*synchronized*/ double getBalance(String name) {
return this.balance;
}

public static void main(String[] args) {
Account a = new Account();
new Thread(()->a.set("zhangsan", 100.0)).start();

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(a.getBalance("zhangsan"));

try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(a.getBalance("zhangsan"));
}
}

再来看synchronized的另外一个属性:可重入,是synchronized必须了解的一个概念。

如果是一个同步方法调用另外一个同步方法,有一个方法加了锁,另外一个方法也需要加锁,加的是同一把锁也是同一个线程,那这个时候申请仍然会得到该对象的锁。比如说是synchronized可重入的,有一个方法m1 是synchronized有一个方法m2也是synchrionzed,m1里能不能调m2。我们m1开始的时候这个线程得到了这把锁,然后在m1里面调用m2,如果说这个时候不允许任何线程再来拿这把锁的时候就死锁了。这个时候调m2它发现是同一个线程,因为你m2也需要申请这把锁,它发现是同一个线程申请的这把锁,允许,可以没问题,这就叫可重入锁。

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
/**
* 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁。
* 也就是说synchronized获得锁是可重入的
* synchronized
* @author mashibing
*/
package com.mashibing.juc.c_009;

import java.util.concurrent.TimeUnit;

public class T {
synchronized void m1() {
System.out.println("m1 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
System.out.println("m1 end");
}

synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2");
}

public static void main(String[] args) {
new T().m1();
}
}

模拟一个父类子类的概念,父类synchronized,子类调用super.m的时候必须得可重入,否则就会出问题(调用父类是同一把锁)。所谓的重入锁就是你拿到这把锁之后不停加锁加锁,加好几道,但锁定的还是同一个对象,去一道就减个1,就是这么个概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.mashibing.juc.c_010;

import java.util.concurrent.TimeUnit;

public class T {
synchronized void m() {
System.out.println("m start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m end");
}

public static void main(String[] args) {
new TT().m();
}
}

class TT extends T {
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}

2DPE_pics/clip

# 人体姿态估计(Human Pose Estimation)经典方法整理

人体姿态估计(Human Pose Estimation)经典方法整理

前言

上学期搬砖期间做了一些pose estimation相关的工作,但一直没有系统的整理过相关方法。近日疫情当头封闭在家,闲暇之余重温了一下之前看过的论文,对pose estimation的部分经典方法做了一个整理。内容大多为个人对这些方法的理解,所以本文也算是一个论文笔记合集吧。

本文涉及到的工作仅包含个人读过的部分论文,按时间顺序进行了整理,并不能代表pose estimation这一领域完整的发展历程。比如bottom-up中的PersonLab、PifPaf,以及传统top-down/bottom-up方法之外的一些single-stage方法等,都是近年来出现的一些值得研究的工作,由于个人还没有仔细了解过暂时没写,之后可能会继续更新

本文目录如下:

· top-down

o CPM

o Hourglass

o CPN

o Simple Baselines

o HRNet

o MSPN

· bottom-up

o openpose

o Hourglass+Associative Embedding

o HigherHRNet

在整理过程中,我参照了以下几篇文章的思路,也添加了一些个人的理解。这几篇文章整理的思路都非常清晰,作者的水平也都比我高得多,推荐大家阅读。

俞刚:人体姿态估计的过去,现在,未来zhuanlan.zhihu.com哇噻:重新思考人体姿态估计 Rethinking Human Pose Estimationzhuanlan.zhihu.comA 2019 guide to Human Pose Estimation with Deep Learningnanonets.com

正文开始之前还是希望各位带着批判的眼光阅读,毕竟本人目前大三水平有限,可能有理解不到位或不正确的地方,希望大家批评指正,欢迎大家评论区或私信随时交流。

Review of 2D Human Pose Estimation with Deep Learning

人体姿态估计(Human Pose Estimation)是计算机视觉中的一个重要任务,也是计算机理解人类动作、行为必不可少的一步。近年来,使用深度学习进行人体姿态估计的方法陆续被提出,且达到了远超传统方法的表现。在实际求解时,对人体姿态的估计常常转化为对人体关键点的预测问题,即首先预测出人体各个关键点的位置坐标,然后根据先验知识确定关键点之间的空间位置关系,从而得到预测的人体骨架。

姿态估计问题可以分为两大类:2D姿态估计3D姿态估计。顾名思义,前者是为每个关键点预测一个二维坐标 ;后者是为每个关键点预测一个三维坐标 ,增加了一维深度信息。本文主要介绍2D姿态估计。

对于2D姿态估计,当下研究的多为多人姿态估计,即每张图片可能包含多个人。解决该类问题的思路通常有两种:top-down和bottom-up:

· top-down的思路是首先对图片进行目标检测,找出所有的人;然后将人从原图中crop出来,resize后输入到网络中进行姿态估计。换言之,top-down是将多人姿态估计的问题转化为多个单人姿态估计的问题

· bottom-up的思路是首先找出图片中所有关键点,然后对关键点进行分组,从而得到一个个人。

通常来说,top-down具有更高的精度,而bottom-up具有更快的速度。下面分别对这两种思路的经典算法展开介绍。

Top-down

由上文我们知道,top-down的方法将多人姿态估计转换为单人姿态估计,那么网络的输入就是包含一个人的bounding box,网络预测的是人的 个关键点坐标。对于关键点的ground truth(对应网络的输出)如何表示有两种思路:

· ,即直接对坐标进行回归,网络的输出是经过fc层输出的 个数字

· 个heatmap,即为每个关键点预测一个heatmap作为关键点的中间表示,heatmap上的最大值处即对应关键点的坐标。对于改种方法,heatmap的ground truth是以关键点为中心的二维高斯分布(高斯核大小为超参)

早期的工作如DeepPose多为直接回归坐标,当下的工作多数以heatmap作为网络的输出,这种中间表示形式使得回归结果更加精确。接下来我们介绍top-down发展过程中的一些landmark。

需要说明的是,CPM和Hourglass提出时主要面向的是单人姿态估计,因为当时还没有COCO数据集,多使用MPII数据集进行evaluation。但top-down的方法本质上也是在解决单人姿态估计问题,所以将这两个模型放在此处介绍。

CPM

CPM,Convolutional Pose Machines,是2015年CMU的一个工作。这个工作提出了很重要的一点:使用神经网络同时学习图片特征(image features)和空间信息(spatial context),这是处理姿态估计问题必不可少的两样信息。在此之前,我们多使用CNN来提取图片特征,使用graphical model或其他模型来表达各个身体部位在空间上的联系。使用神经网络同时学习这两种信息,不仅效果更好,而且使端到端(end-to-end)学习成为可能。

CPM是一个multi-stage的结构,如下图所示。每个stage的输入是原始图片特征和上一个阶段输出的belief map,这个belief map可以认为是之前的stage学到的spatial context的一个encoding。这样当前stage根据这两种信息继续通过卷积层提取信息,并产生新的belief map,这样经过不断的refinement,最后产生一个较为准确的结果。

截图里有图片  描述已自动生成

下面这张图给出了一个具体的例子来强调spatial context的重要性,也展现了refinement的过程。在stage1时,错误的将right elbow定位到了right knee处;在stage2时,由于有了stage1提供的spatial context信息,我们根据right shoulder的位置能够对right elbow的位置进行纠正,从图中可以看到响应的峰值已经基本到了正确位置。在stage3时,stage2中一些错误的响应(left elbow、right knee)彻底被消除,right elbow的定位也更加精确。

图片包含 女人, 钟表, 站, 华美  描述已自动生成

这篇文章还提出了很重要的一点,就是通过intermediate supervision来解决梯度消失的问题。由于我们的网络可以包含许多个stage,随着网络的加深,梯度消失问题的出现导致网络无法收敛。因此,在每个stage结束后我们给当前的belief map加一个监督,可以有效缓解梯度消失的问题,后面可以看到在multi-stage的网络中这是比较常用的一种技巧。

Hourglass

Hourglass也是一种multi-stage的结构,是2016年的一个工作,与CPM相比更加简洁一些,如下图所示。这个网络由多个堆叠起来的Hourglass module组成(因为网络长的很像多个堆叠起来的沙漏)。每个Hourglass module的结构都包含一个bottom-up过程和一个top-down过程,前者通过卷积和pooling将图片从高分辨率降到低分辨率,后者通过upsample将图片从低分辨率恢复到高分辨率。

图片包含 游戏机, 画, 桌子  描述已自动生成

每个Hourglass module的结构如下图所示,其中每个box都是一个残差结构。可以看到在top-down阶段,对于两个相邻分辨率的feature map,我们通过upsampling(这里用的是nearest neighbor upsampling)对较低分辨率的feature map进行上采样,然后通过skip connection将bottom-up阶段较高分辨率的feature map拿过来,此时再通过element-wise addition将这两部分特征进行合并。通过这种方式,在top-down阶段将不同分辨率下的特征逐步进行了融合。

图片包含 游戏机, 桌子, 体育  描述已自动生成

总而言之,Hourglass最大的特点就是能够提取并整合所有scale下的特征,这种设计的出发点也是出于我们在姿态估计时对各个不同scale下特征的需要。这种网络结构在当时达到了很好的效果。

值得注意的是,Hourglass也使用了intermediate supervision,对于multi-stage的网络这一点通常是必要的。

CPN

CPN,Cascaded Pyramid Network,是2017年旷视提出的一种网络结构,获得了COCO 2017 Keypoint Benchmark的冠军,网络结构如下图所示。这个网络可以分为两部分:GlobalNet和RefineNet,从名字也可以看出后半部分网络是在前半部分的基础上做refinement。GlobalNet的作用主要是对关键点进行一个初步的检测,由于使用了强大的ResNet作为backbone,网络能够提取到较为丰富的特征(在此之前的CPM、Hourglass都没有使用,因此网络的特征提取能力较差),并且使用了FPN结构加强了特征提取,在这个过程中像head、eyes这些简单且可见的关键点基本能够被有效地定位。而对于GlobalNet没有检测到的关键点,使用RefineNet进行进一步的挖掘,RefineNet实际上是将pyramid结构中不同分辨率下的特征进行了一个整合,这样一些被遮挡的、难以定位的关键点,根据融合后的上下文语境信息能够更好的被定位到。

手机屏幕截图  描述已自动生成

下面这张图给出了一个例子(绿点表示ground truth),对于eye来说较容易定位,通过GlobalNet即可定位到。而对于hip来说,在原图中被遮挡,仅仅使用GlobalNet难以直接精确定位。通过RefineNet将语境信息整合进来,才使得这些关键点被定位。

图片包含 游戏机, 画  描述已自动生成

Simple Baselines

Simple Baselines,是2018年MSRA的工作,网络结构如下图所示。之所以叫这个名字,是因为这个网络真的很简单。该网络就是在ResNet的基础上接了一个head,这个head仅仅包含几个deconvolutional layer,用于提升ResNet输出的feature map的分辨率,我们提到过多次高分辨率是姿态估计任务的需要。这里的deconvolutional layer是一种不太严谨的说法,阅读源代码可知,deconvolutional layer实际上是将transpose convolution、BatchNorm、ReLU封装成了一个结构。所以关键之处在于transpose convolution,可以认为是convolution的逆过程。

从图中看可以发现Simple Baselines的网络结构有点类似Hourglass中的一个module,但可以发现:①该网络没有使用类似Hourglass中的skip connection;②该网络是single-stage,Hourglass是multi-stage的。但令人惊讶的是,该网络的效果却超过了Hourglass。我个人认为有两点原因,一是Simple Baselines以ResNet作为backbone,特征提取能力相比Hourglass更强。二是Hourglass中上采样使用的是简单的nearest neighbor upsampling,而这里使用的是deconvolutional layer,后者的效果更好(后面可以看到在MSRA的Higher-HRNet中依旧使用了这种结构)。

图片包含 游戏机, 画, 钟表  描述已自动生成

HRNet

HRNet,是2019年MSRA的工作,网络结构如下图所示,这个网络结构和之前的相比还是很novel的。我们知道,要想准确定位关键点,既离不开高分辨率的表示,也离不开低分辨率的语义信息。之前提到的几种网络结构也都着力于充分利用多个分辨率的信息,但之前的网络比如Hourglass、Simple Baselines,都是经历了一个先bottom-up再top-down的过程,换言之,我们得到的高分辨率的feature map都是从低分辨率的feature map经过upsampling/deconvolution恢复过来的,这样总会有一些潜在的信息丢失。HRNet的最大特点就是将不同分辨率feature map之间的连接从串行改成了并行,从图中可以清楚的看到,在整个过程中一直保持高分辨率分支的存在,并且随着网络的深入,逐渐添加低分辨率的分支,最后网络结构就是同时保持多个分辨率分支的并行网络

图片包含 游戏机  描述已自动生成

此外非常重要的一点,从图中我们也可以看到,为了使不同分辨率之间的信息融合在一起,从而获得包含多个分辨率下信息的feature map,在相邻的stage之间(每添加一个新的低分辨率分支时视为一个新stage的开始)加了fusion结构,fusion直观上类似在两边的feature maps之间加了一个fc层,下采样通过多个 卷积,上采样就是简单的nearest neighbor upsampling。这里的融合就是简单的累加。通过多次fusion,最后得到的feature map包含的信息是非常丰富的,也因此HRNet达到了很好的效果。

图片包含 游戏机, 钟表  描述已自动生成

值得一提的是,MSRA后来在HRNet上做了一点微小的改动,称为HRNetv2。HRNetv1输出的feature map(heatmap)是最后一层最高分辨率的feature map,HRNetv2输出的feature map(heatmap)是最后一层所有分辨率的feature map变换scale后concat在一起。之后他们将该任务应用到了object detection、semantic segmentation、facial landmark detection等其他多个任务上,在许多数据集上达到了SOTA。

MSPN

MSPN,是2019年旷视的工作,网络结构如下图所示。此前我们介绍的网络中,有single-stage的例如CPN、Simple Baselines,也有multi-stage的如CPM、Hourglass,理论上multi-stage的效果应该更好,但实际上在COCO数据集上single-stage的表现超过了multi-stage。MSPN沿用了multi-stage的思路,并做出了一系列改进,最终使得MSPN的效果超过了当前的single-stage网络。

首先,对于每个stage的module,MSPN采用了前面提到的CPN中的GlobalNet(backbone为ResNet),当然这里使用其他backbone也没问题。其次,MSPN增加了跨阶段的特征融合,即图中的黄色箭头。由于经过反复的下采样-上采样,会有不可避免的信息损失,故MSPN中将前一个stage下采样和上采样过程中对应分辨率的两个feature maps都连接过来,与当前stage下采样的feature map进行融合,这样当前stage得到的feature map就包含了更多prior information,减少了stage之间的信息丢失。此外,这种类似残差结构的设计也有助于缓解梯度消失问题。最后,MSPN还采用了coarse-to-fine supervision,我们知道姿态估计的ground truth是以关键点为中心的二维高斯分布,这里的高斯核大小是一个超参数。直观上来说,对于multi-stage网络,随着stage的增加,我们对keypoint的估计是一个coarse-to-fine的过程,这样我们进行intermediate supervision的时候,可以将ground truth也设置成coarse-to-fine的heatmap,也就是前面stage的高斯核较大,后面stage的高斯核较小,随着stage的增加要求关键点位置越来越精确。

手机屏幕截图  描述已自动生成

Bottom-up

前面我们提到诸多top-down的方法都是将多人姿态估计转化为单人姿态估计问题,但在进行多人姿态估计时,top-down的方法有两个缺点:①性能受到detector性能的影响(虽然在CPN的论文中证明了当detector性能足够好时提高detector的性能对最后的结果提高很微小);②运行时间随着图片中人数增加而增加。而bottom-up的方法则是另一条思路:先检测出图中所有人的所有关键点,再对关键点进行分组,进而组装成多个人。这种方法的好处是性能受人数影响较小,实际上bottom-up的速度往往会比top-down的更快,几乎能做到real-time,在移动端部署的时候往往采用的也是bottom-up的方法。接下来介绍近年出现的一些有代表性的bottom-up的工作。

Openpose

Openpose是2016年CMU的一个工作,获得了COCO 2016 Keypoints challenge的冠军,这个工作在后来产生了很大的影响,github上目前有15.8k个stars。下图是openpose的pipeline,首先根据输入图片(a)生成一个Part Confidence Maps(b)和一个Part Affinity Fields(c):前者就是我们上文常提到的heatmap,用来预测关键点的位置;后者是本文的一个主要创新点)——PAF,PAF实际上是在关键点之间建立的一个向量场,描述一个limb的方向。有了heatmap和PAF,我们使用二分图最大权匹配算法来对关键点进行组装(d),从而得到人体骨架(e)。由于文字描述过于抽象,下面花一些篇幅引用原文中的一些数学表达形式来分别介绍这几部分。

图片包含 照片, 球, 球拍, 监控  描述已自动生成

对于生成heatmap和PAF的部分,使用了下图所示的网络结构。首先 是初始的feature map,网络的输出是heatmap和PAF,分别用 和 表示,其中 是part(即上文中的keypoint,这里遵循原文中的说法)的数量, 是limb的数量。 ,即第 个part对应的heatmap,每个pixel对应一个响应值,可以认为是概率值; ,即第 个limb对应的PAF,每个pixel对应一个二维向量,表示该pixel所在limb的方向。二者都是pixel-wise的。这里借鉴CPM使用了多个stage不断进行refinement,后一阶段的输入是前一阶段的 、 和初始的 的结合。

手机屏幕截图  描述已自动生成

接下来需要说明heatmap和PAF的ground truth分别是怎样的。heatmap和top-down中的类似,对于第 个人的第 个part,令其位置为 ,则ground truth是以 为中心的二维高斯分布,用 表示。在这里由于一张图片中有多个人,ground truth中的每个channel对应一个part,第 个part对应的ground truth为 , 表示单个位置,即对所有人该part的ground truth按pixel取max

PAF的ground truth则更加复杂,对于第 个人的第 个limb(连接关键点 和 ),ground truth用 表示。如果位置 在这个limb上, ,否则为零向量。这里 实际上是 指向 的单位向量。那么如何判断一个位置 是否在当前limb上呢?只要 满足:①在线段 上,②距离线段 在一个阈值范围内(框处了一个矩形范围),就认为 在该limb上。最后对于某个limb的所有,按pixel采用average进行处理,即 ,这里 为 位置处非零向量的个数,也就是只有非零向量才参与均值计算。此处用到的一些符号在图中含义如下。

图片包含 游戏机, 刀  描述已自动生成

至于loss的计算,就是对每个stage的 和 分别计算L2 loss,最后所有stage的loss相加即可。同样类似之前top-down中提到的,intermediate supervision有助于缓解梯度消失的问题。

现在我们有了heatmap,在heatmap上采用nms可以为每个part找出一系列候选点(因为图中有不止一个人,以及false positive的点),这些候选点之间互相组合能够产生大量可能的limb,如何从中选出真正的limb,这就需要使用我们预测的PAF。首先定义两个关键点 和 之间组合的权值 ,这里 , 分别表示 的坐标。实际上就是 间各点的PAF在线段 上投影的积分,直观上说,如果线段上各点的PAF方向与线段的方向越一致, 就越大,那么这两个点组成一个limb的可能性就越大。

知道了如何计算两个候选点之间组合的权值,对于任意两个part对应的候选点集合,我们使用二分图最大权匹配算法即可给出一组匹配。但如果我们考虑所有part之间的PAF,这将是一个K分图最大权匹配问题,该问题是NP-Hard的。因此我们做一个简化,我们抽取人体骨架的一棵最小生成树,这样对每条树边连接的两个part采用二分图最大权匹配来求解,不同树边之间是互不干扰的。也就是将原问题划分成了若干个二分图最大权匹配问题,最后将每条树边对应的匹配集合组装在一起即可得到人体骨架。

图片包含 游戏机, 画, 钟表  描述已自动生成

上述即openpose的整个pipeline,虽然看起来比较复杂,但许多想法都是围绕PAF展开的,毕竟PAF还是这篇文章最大的亮点。

Hourglass+Associative Embedding

Associative embedding,是一种处理detection+grouping任务的方法。也就是先检测再组装的任务,比如多人姿态估计是检测出所有关键点再组装成多个人体,实例分割是先检测出所有像素点的语义类别再组装成多个实例。往往这类任务都是two-stage的,即先detection,再grouping。这篇文章的作者提出了一种新的思路:同时处理detection和grouping两个任务,这么做的初衷是因为detection和grouping两个任务之间本身密切相关,分成两步来先后处理可能会导致性能下降。作者将该方法应用到了Hourglass上,在当时达到了多人姿态估计任务的SOTA。

在单人姿态估计时,我们网络的输出是m个heatmap,m表示关键点数量。现在在之前的基础上多输出了m个associative embeddings。Associative embeddings实际上是给heatmap的每个值都额外嵌入一个vector作为一个tag用于标识所在的组,拥有相同tag的所有关键点我们将其划分为一组,也就是属于某一个人。下图是Hourglass+Associative embedding的示意图,Hourglass输出了m个detection heatmaps(灰色)和m个associative embeddings(蓝色),根据heatmaps上的响应值确定关键点的坐标,根据embeddings上的tag(下图中不同tag用不同颜色表示)确定哪些关键点属于同一个人。

卡通人物  描述已自动生成

现在我们考虑ground truth,对于heatmaps来说和之前一样,不再赘述。对于embeddings来说实际上没有ground truth,因为tag的绝对值并不重要,只要保证同一个人的关键点tag相同,不同人的关键点tag之间互不相同即可。这里需要说明的是,tag虽然是一个vector,但vector的维数并不重要,实践证明1D vector已经足够(即一个实数)。为了使tag满足上述要求,我们需要设计一个loss来评价当前的tag是否符合实际的分组。用 表示第 个关键点对应的embeddings(tagging heatmap), 表示人的数量, 表示关键点数量, 表示第 个人第 个关键点的坐标。我们定义第 个人的参考embeddings为 ,则loss定义为 。loss的前半部分使同一个人的所有embeddings尽量接近,后半部分 使不同人的embeddings间隔尽量增大。最后所有关键点的embeddings分布如下图所示。inference时将距离小于一定阈值的embeddings分为一组即可。

图片包含 游戏机  描述已自动生成

HigherHRNet

HigherHRNet,是微软在HRNet之后延续的一个工作。前面我们提到过HRNet在top-down的方法中表现的很好,是因为这种并行的结构使得最后的feature map能够包含各个分辨率的信息,尤其是对高分辨率信息保留的效果较之前提升尤为明显。在bottom-up的方法中,作者认为有两个问题需要解决:①scale variation,即图片中有各种scale的人,如果仅仅在一个分辨率的feature map下进行预测,难以保证每个scale的人的关键点都能被精确预测;②精确定位small person的关键点。之前一些网络在推理时使用multiscale evaluation,能够缓解问题①,但仍然无法解决问题②,对small person的预测不够精确。

HigherHRNet的思路是首先使用HRNet生成feature map(最高分辨率分支),然后接一个类似Simple Baselines中的deconvolution层,生成一个更高分辨率的feature map。显然,更高分辨率的feature map有助于更加精确地定位small person的关键点(实践证明接一层deconv. module足够)。在训练时,使用multi-resolution supervision,即对原图1/4和1/2大小的两个feature map同时进行监督,这样做是为了在训练时就使网络获得处理scale variation的能力,1/4的feature map主要处理大一些的人,1/2的feature map主要处理小一些的人,而不是在推理时依赖multiscale evaluation处理scale variation的问题。在推理时,使用multi-resolution heatmap aggregation,即将不同分辨率的heatmap取平均用于最后的预测,也是为了处理scale variation。

图片包含 游戏机  描述已自动生成

前面仅仅讨论了如何生成一个准确且scale-aware的heatmap,对于grouping采用的也是上文提到的associative embedding。最后这个工作达到了SOTA,是目前bottom-up方法中性能最强劲的网络之一。

参考文献

· Convolutional Pose Machines, Wei etc, CVPR 2016

· Stacked Hourglass Networks for Human Pose Estimation, Newell etc, ECCV 2016

· Cascaded Pyramid Network for Multi-Person Pose Estimation, Chen etc, CVPR 2017

· Simple Baselines for Human Pose Estimation and Tracking, Xiao etc, ECCV 2018

· Deep High-Resolution Representation Learning for Human Pose Estimation, Sun etc, CVPR 2019

· Rethinking on Multi-Stage Networks for Human Pose Estimation, Li etc, Arxiv 2019

· Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields, Cao etc, CVPR 2017

· Associative Embedding: End-to-End Learning for Joint Detection and Grouping, Newell etc, NIPS 2017

· Bottom-up Higher-Resolution Networks for Multi-Person Pose Estimation, Cheng etc, Arxiv 2019

编辑于 2020-02-03

ZSL_pics/clip

零次学习(Zero-Shot Learning)入门

零次学习(Zero-Shot Learning)入门

转载自 https://zhuanlan.zhihu.com/p/34656727

很久没有更文章了,主要是没有找到zero-shot learning(ZSL)方面我特别想要分享的文章,且中间有一段时间在考虑要不要继续做这个题目,再加上我懒 (¬_¬),所以一直拖到了现在。

最近科研没什么进展,就想着写一个ZSL的入门性的文章,目的是为了帮助完全没有接触过这方面,并有些兴趣的同学,能在较短的时间对ZSL有一定的认识,并且对目前的发展情况有一定的把握。

在此之前,需要提到的是:无论是论文笔记,还是总结性的读物,都包含了作者自己的理解和二次加工,想要做出好的工作必定需要自己看论文和总结。

零次学习(zero-shot learning)基本概念

每次在实验室做工作汇报的时候,总会把ZSL的基本概念讲一遍,但是每次的效果都不是很好,工作都讲完了,提的第一个问题依然是:ZSL到底是什么?这让我一度认为我的表达能力有问题。。。。。。不过回忆起我第一次接触这个题目的时候,也花了挺长的时间才搞清楚到底在做一件什么事情,那篇入门的文章[1]看了很久才基本看懂。因此,我尽量用最简单的,不带任何公式的方式来讲一下这到底是个什么问题。

假设小暗(纯粹因为不想用小明)和爸爸,到了动物园,看到了马,然后爸爸告诉他,这就是马;之后,又看到了老虎,告诉他:“看,这种身上有条纹的动物就是老虎。”;最后,又带他去看了熊猫,对他说:“你看这熊猫是黑白色的。”然后,爸爸给小暗安排了一个任务,让他在动物园里找一种他从没见过的动物,叫斑马,并告诉了小暗有关于斑马的信息:“斑马有着马的轮廓,身上有像老虎一样的条纹,而且它像熊猫一样是黑白色的。”最后,小暗根据爸爸的提示,在动物园里找到了斑马(意料之中的结局。。。)。

上述例子中包含了一个人类的推理过程,就是利用过去的知识(马,老虎,熊猫和斑马的描述),在脑海中推理出新对象的具体形态,从而能对新对象进行辨认。(如图1所示)ZSL就是希望能够模仿人类的这个推理过程,使得计算机具有识别新事物的能力。

社交网络的地图  描述已自动生成图1 ZSL概念图[17]

如今深度学习非常火热,使得纯监督学习在很多任务上都达到了让人惊叹的结果,但其限制是:往往需要足够多的样本才能训练出足够好的模型,并且利用猫狗训练出来的分类器,就只能对猫狗进行分类,其他的物种它都无法识别。这样的模型显然并不符合我们对人工智能的终极想象,我们希望机器能够像上文中的小暗一样,具有通过推理,识别新类别的能力。

ZSL就是希望我们的模型能够对其从没见过的类别进行分类,让机器具有推理能力,实现真正的智能。其中零次(Zero-shot)是指对于要分类的类别对象,一次也不学习。这样的能力听上去很具有吸引力,那么到底是怎么实现的呢?

假设我们的模型已经能够识别马,老虎和熊猫了,现在需要该模型也识别斑马,那么我们需要像爸爸一样告诉模型,怎样的对象才是斑马,但是并不能直接让模型看见斑马。所以模型需要知道的信息是马的样本、老虎的样本、熊猫的样本和样本的标签,以及关于前三种动物和斑马的描述。将其转换为常规的机器学习,这里我们只讨论一般的图片分类问题:

(1)训练集数据 及其标签 ,包含了模型需要学习的类别(马、老虎和熊猫),这里和传统的监督学习中的定义一致;

(2)测试集数据 及其标签 ,包含了模型需要辨识的类别(斑马),这里和传统的监督学习中也定义一直;

(3)训练集类别的描述 ,以及测试集类别的描述 ;我们将每一个类别 ,都表示成一个语义向量 的形式,而这个语义向量的每一个维度都表示一种高级的属性,比如“黑白色”、“有尾巴”、“有羽毛”等等,当这个类别包含这种属性时,那在其维度上被设置为非零值。对于一个数据集来说,语义向量的维度是固定的,它包含了能够较充分描述数据集中类别的属性。

在ZSL中,我们希望利用 和 来训练模型,而模型能够具有识别 的能力,因此模型需要知道所有类别的描述 和 。ZSL这样的设置其实就是上文中小暗识别斑马的过程中,爸爸为他提供的条件。

图片包含 游戏机, 文字, 画  描述已自动生成图2 ZSL设置图[16]

如图2,可以较为直观地了解ZSL的设置。

讲到这,很多同学可能会问:

(1)类别的描述 到底是怎么获取的?

答:有人工专家定义的,也有通过海量的附加数据集自动学习出来的,但前者的效果目前要好很多。

(2)这样做让人觉得有点失望呀!我希望模型能够在没有斑马样本的情况下,识别斑马,而现在,虽然我不需要为模型提供斑马的样本,但是却要为每一个类别添加一种描述,更离谱的是我还需要斑马(测试集)的描述,这个过程并没有想象中智能诶!

答:的确,在我们的想象中,我们期待的智能是:只给机器马、老虎和熊猫,然后它就可以识别斑马了,这样多爽,多神奇。但我们回过头去,再想想小暗的思考过程,如果爸爸不告诉小暗关于斑马的任何信息,那么当小暗看见斑马的时候,并不会知道它是什么,只是小暗能够描述它:“这是一匹有着黑白颜色条纹的马。”这里,有同学可能又会说:至少我们可以不用告诉小暗类别的描述呀,但是ZSL就不行。其实,我们是需要告诉小暗类别描述的,或者说小暗在之前就学习到了类别描述,比如怎样的图案是“条纹”,怎样的颜色称为“黑白色”,这样的属性定义。对于一个模型来说,它就像刚出生的婴儿,我们需要教会它这些属性的定义。

(3)就算是这样,需要实现定义这个描述 还是很蛋疼的一件事情。

答:(1)中就有提到,描述 可以自动学习,我们将小暗已经掌握的知识描述为一个知识库,这个知识库里就有对各种属性的定义;而能够模仿人类知识库的最好东西就是“百度百科”,“维基百科”等等各种百科,我们可以利用百科中的各种定义,生成类别的定义,这方面侧重于NLP,因此不进一步讨论。

在此,我们小小总结一下ZSL问题的定义。利用训练集数据训练模型,使得模型能够对测试集的对象进行分类,但是训练集类别和测试集类别之间没有交集;期间需要借助类别的描述,来建立训练集和测试集之间的联系,从而使得模型有效

目前的研究方式

在上文中提到,要实现ZSL功能似乎需要解决两个部分的问题:第一个问题是获取合适的类别描述 ;第二个问题是建立一个合适的分类模型。

目前大部分工作都集中在第二个问题上,而第一个问题的研究进展比较缓慢。个人认为的原因是, 目前 的获取主要集中于一些NLP的方法,而且难度较大;而第二个问题能够用的方法较多,比较容易出成果。

因此,接下来的算法部分,也只介绍研究分类模型的方法。

数据集介绍

先介绍数据集,是因为希望在算法介绍部分,直接给出实例,让大家能够直接上手,这里顺便插个沐神

@李沐

的感悟。

虽然在我认识的人里,好些人能够读一篇论文或者听一个报告后就能问出很好的问题,然后就基本弄懂了。但我在这个上笨很多。读过的论文就像喝过的水,第二天就不记得了。一定是需要静下心来,从头到尾实现一篇,跑上几个数据,调些参数,才能心安地觉得懂了。例如在港科大的两年读了很多论文,但现在反过来看,仍然记得可能就是那两个老老实实动手实现过写过论文的模型了。即使后来在机器学习这个方向又走了五年,学习任何新东西仍然是要靠动手。——李沐(MXNet开发者)

(1)Animal with Attributes(AwA)官网:Animals with Attributes

提出ZSL定义的作者,给出的数据集,都是动物的图片,包括50个类别的图片,其中40个类别作为训练集,10个类别作为测试集,每个类别的语义为85维,总共有30475张图片。但是目前由于版权问题,已经无法获取这个数据集的图片了,作者便提出了AwA2,与前者类似,总共37322张图片。

(2)Caltech-UCSD-Birds-200-2011(CUB)官网:Caltech-UCSD Birds-200-2011

全部都是鸟类的图片,总共200类,150类为训练集,50类为测试集,类别的语义为312维,有11788张图片。

(3)Sun database(SUN)官网:SUN Database

总共有717个类别,每个类别20张图片,类别语义为102维。传统的分法是训练集707类,测试集10类。

(4)Attribute Pascal and Yahoo dataset(aPY)官网:Describing Objects by their Attributes

共有32个类,其中20个类作为训练集,12个类作为测试集,类别语义为64维,共有15339张图片。

(5)ILSVRC2012/ILSVRC2010(ImNet-2)

利用ImageNet做成的数据集,由ILSVRC2012的1000个类作为训练集,ILSVRC2010的360个类作为测试集,有254000张图片。它由 4.6M 的Wikipedia数据集训练而得到,共1000维。

上述数据集中(1)-(4)都是较小型(small-scale)的数据集,(5)是大型(large-scale)数据集。虽然(1)-(4)已经提供了人工定义的类别语义,但是有些作者也会从维基语料库中自动提取出类别的语义表示,来检测自己的模型。

这里给大家提供一些已经用GoogleNet提取好的数据集图片特征,大家可以比较方便地使用。Zero-Shot Learing问题数据集分享(GoogleNet 提取)

基础算法介绍

在此,只具体介绍最简单的方法,让大家可以快速上手。我们面对的是一个图片分类问题,即对测试集的样本 进行分类,而我们分类时需要借助类别的描述 ,由于每一个类别 ,都对应一个语义向量 ,因此我们现在可以忘掉 ,直接使用 。我们把 (利用深度网络提取的图片特征,比如GoogleNet提取为1024维)称为特征空间(visual feature space),把类别的语义表示 ,称为语义空间。我们要做的,其实就是建立特征空间与语义空间之间的映射

对于分类,我们能想到的最简单的形式就是岭回归(ridge regression),俗称均方误差加范数约束,具体形式为:

(1)

其中, 通常为2范数约束, 为超参,对 求导,并让导为0,即可求出 的值。测试时,利用 将 投影到语义空间中,并在该空间中寻找到离它最近的 ,则样本的类别为 所对应的标签 。

简单写一个matlab实现。

regression_lambda = 1.0;

W = ridge_regression(param.train_set, param.train_class_attributes, regression_lambda , 1024);

S_test = param.test_set * W;

[zsl_accuracy]= zsl_el(S_test, param.S_te, param);

fprintf(‘AwA ZSL accuracy on test set: %.1f%%\n’, zsl_accuracy*100);

我们使用AwA数据集,图片事先利用GoogleNet提取了特征(1024维),在测试集上可以得到59.1%的准确率。

这样一个岭回归之所以有效,是因为训练集类别语义 与测试集类别语义 之间存在的密切联系。其实任何ZSL方法有效的基础,都是因为这两者之间具体的联系。

仅仅利用如此naive的方式,得到的结果显然不能满足我们的要求,那么建立更好的模型,则需要进一步了解ZSL问题中,存在着哪些和传统监督分类的差异。

ZSL中存在的问题

在此,介绍一些目前ZSL中主要存在的问题,以便让大家了解目前ZS领域有哪些研究点。

领域漂移问题(domain shift problem)

该问题的正式定义首先由[2]提出。简单来说,就是同一种属性,在不同的类别中,视觉特征的表现可能很大。如图3所示,斑马和猪都有尾巴,因此在它的类别语义表示中,“有尾巴”这一项都是非0值,但是两者尾巴的视觉特征却相差很远。如果斑马是训练集,而猪是测试集,那么利用斑马训练出来的模型,则很难正确地对猪进行分类。

手机屏幕截图  描述已自动生成图3 domain shift示意图,图中的prototype表示类别在语义空间中的位置[2]

枢纽点问题(Hubness problem)

这其实是高维空间中固有的问题:在高维空间中,某些点会成为大多数点的最近邻点。这听上去有些反直观,细节方面可以参考[3]。由于ZSL在计算最终的正确率时,使用的是K-NN,所以会受到hubness problem的影响,并且[4]中,证明了基于岭回归的方法会加重hubness problem问题。

语义间隔(semantic gap)

样本的特征往往是视觉特征,比如用深度网络提取到的特征,而语义表示却是非视觉的,这直接反应到数据上其实就是:样本在特征空间中所构成的流型与语义空间中类别构成的流型是不一致的。(如图4所示)

图片包含 游戏机, 钟表, 画  描述已自动生成图4 流型不一致示意图[8]

这使得直接学习两者之间的映射变得困难。

还有其他的,比如semantic loss[5]问题,样本通过映射坍塌到一点[6]等,由于还不常研究,在此就不再讨论。

在此,我们给出解决上述三个问题的基本方法,从而更加深度地了解这三个问题。

(1)领域漂移

由于样本的特征维度往往比语义的维度大,所以建立从 到 的映射往往会丢失信息,为了保留更多的信息,保持更多的丰富性,最流行的做法是将映射到语义空间中的样本,再重建回去,这样学习到的映射就能够得到保留更多的信息。因此,在原来简单岭回归[1]的基础上,可以将目标函数改为:[7]

(2)

从目标函数可以看出,这其实完成的是一个简易的自编码器过程,我们简称这个算法为SAE,利用matlab可以轻松对其实现。

lambda1 = 800000;

W = SAE(param.train_set’, param.train_class_attributes’, lambda1);

S_test = param.test_set * NormalizeFea(W’);

[zsl_accuracy]= zsl_el(S_test, param.S_te, param);

fprintf(‘AwA ZSL accuracy on test set: %.1f%%\n’, zsl_accuracy*100);

依然是在AwA上进行测试,可以得到83.2%的准确率,比简单的岭回归(1)提高了24.1%。自编码器的这个结构目前在ZSL方法中非常流行,稍后我们还会提到。

(2)枢纽点问题

目前对于枢纽点问题的解决主要有两种方法:

a. 如果模型建立的方式为岭回归,那么可以建立从语义空间到特征空间的映射,从而不加深hubness problem对结果的影响[4],也就是说将目标函数(1)改为:

(3)

在AwA数据集上,这种简单的改变能够得到76.5%的正确率,比原本提高了17.4%。

b.可以使用生成模型,比如自编码器、GAN等,生成测试集的样本,这样就变成了一个传统的监督分类问题,不存在K-NN的操作,所以不存在hubness problem的影响。

(3)语义间隔问题

语义间隔问题的本质是二者的流形结构不一致,因此,解决此问题的着手点就在于将两者的流形调整到一致,再学习两者之间的映射[8]。最简单的方法自然是将类别的语义表示调整到样本的流型上,即用类别语义表示的K近邻样本点,重新表示类别语义即可。

有关ZSL的一些其他的概念

这里将提到一些ZSL涉及到的其他概念。

(1)直推式学习(Transductive setting)

这里的直推式学习其实是指在训练模型的时候,我们可以拿到测试集的数据,只是不能拿到测试集的样本的标签,因此我们可以利用测试集数据,得到一些测试集类别的先验知识。这种设置在迁移学习中很常见。

图片包含 游戏机, 文字  描述已自动生成图5 非直推式(inductive)和直推式学习的区别[16]

(2)泛化的ZSL(generalized ZSL)

上文中提到的ZSL,在测试时使用K-NN进行正确率的评估时,只在测试类别中找最近邻的类别,但是在现实的问题中,拿到的样本也可能属于训练集类别,因此在测试时,同时加入训练集类别。[9]现在的很多方法都开始测试模型在这种设置下的能力。

推荐阅读的论文

我一直不想写ZSL的发展史,因为据我的经验,写了一大段发展史之后,往往大家的兴致不高,而且看完之后一般都不会有什么特别的感觉,基本也记不得什么东西。所以倒不如给大家推荐一些论文,从最早的到目前最新的,使得大家在短时间内能对ZSL的发展有一个大概的概念。

(1)Learning To Detect Unseen Object Classes by Between-Class Attribute Transfer[1]

ZSL问题的开创性文章,当然是必读的喽,而且可以顺便看看别人是如何阐述一个新问题(挖坑)的。

(2)An embarrassingly simple approach to zero-shot learning[10]

有着很强的理论基础,算法简单、有效,虽然已经过去很多年了,但还是目前新工作需要进行对比的方法之一。

(3)Transductive Multi-View Zero-Shot Learning[2]

第一次定义了domain shift问题。

(4)Zero-shot recognition using dual visualsemantic mapping paths[11]

解决semantic gap问题的简单做法。

(5)Predicting visual exemplars of unseen classes for zero-shot learning[12]

从本质的角度出发,将ZSL问题,看作聚类问题,用最简单的方法直接建立映射。

(6)Semantic Autoencoder for Zero-Shot Learning[7]

引入自编码器结构的第一篇文章,直接导致现在出现的新方法大都具有这种结构。

(7)Zero-Shot Learning - A Comprehensive Evaluation of the Good, the Bad and the Ugly[14]

综述性的文章,总结了17年底以前的方法,提出了新的评价标准,对当时领域发展比较混乱的地方做出了一些更标准的评估。

(8)Zero-Shot Learning via Class-Conditioned Deep Generative Models[6]

将[7]改造为深度模型,并加上一些其他的约束。

(9)Preserving Semantic Relations for Zero-Shot Learning[13]

在自编码器结构的基础上,显示地加入语义类别之间的关系约束。

(10)Recent Advances in Zero-shot Recognition[15]

综述性的文章,读起来很顺畅,可以看看别人是怎么写综述,中顶刊的。

以上几篇文章,是我认为较有代表性,比较值得读的工作。

代码

有很多工作,作者都是提供代码的,我自己也实现了一些工作,如果有时间我会将其整理在一起,方便大家使用。

我自己的看法

我当初做这个课题,纯粹是因为项目的需要,再加上当时并没有想清楚自己要做什么,所以就做着试试了。目前这个领域属于很好发论文的阶段,而且并不需要十分深刻的理解,就能发不错等级的文章,比较容易能够看到它的发展趋势及下一步大家扎堆的地方,很多时候是在拼速度,而不是拼想法。但好发论文,并不代表它发展迅速,在我看来,真正有贡献的工作少之又少,且其对本质的研究发展缓慢。并且,该问题离实际应用还太远,很可能并不属于这个时代。基于这些原因,之前有一段时间很不想再继续这个课题。。。

总结

稍微总结一下,其实我也不知道要总结什么,只是习惯了每篇文章最后都要写个总结。花了大概一天的时间,写了这篇ZSL入门文章。写它一方面是因为希望能够有一篇ZSL的入门性质的读物,为大家提供便利;另一方面就是近期科研不顺,总是怀疑自己不是读书的料,写写文章让自己心情好些。希望大家阅读之后,能够得到一定的帮助吧!

其他

文章仓促之下写的,没有经过什么构思,就是想到哪,写到哪。后面我应该还会修改,添加一些其他的内容,如果大家有什么问题,欢迎评论或者私信。

祝大家科研顺利!为人类理解这个世界做一点点贡献!

参考文献

[1]Learning To Detect Unseen Object Classes by Between-Class Attribute Transfer

[2]Transductive Multi-View Zero-Shot Learning.

[3]Hubness and Pollution: Delving into Class-Space Mapping for Zero-Shot Learning.

[4]Ridge Regression, Hubness, and Zero-Shot Learning.

[5]Zero-Shot Visual Recognition using Semantics-Preserving Adversarial Embedding Network.

[6]Zero-Shot Learning via Class-Conditioned Deep Generative Models.

[7]Semantic Autoencoder for Zero-Shot Learning.

[8]Zero-Shot Recognition using Dual Visual-Semantic Mapping Paths.

[9]An Empirical Study and Analysis of Generalized Zero-Shot Learning for Object Recognition in the Wild.

[10]An embarrassingly simple approach to zero-shot learning

[11]Zero-shot recognition using dual visualsemantic mapping paths

[12]Predicting visual exemplars of unseen classes for zero-shot learning

[13]Preserving Semantic Relations for Zero-Shot Learning

[14]Zero-Shot Learning - A Comprehensive Evaluation of the Good, the Bad and the Ugly

[15]Recent Advances in Zero-shot Recognition

[16]http://people.duke.edu/~ww107/material/ZSL.pdf

[17]Attribute-Based Synthetic Network (ABS-Net): Learning More From Pseudo Feature Representation

编辑于 2018-04-17

LaTeX公式手册(全网最全)

LaTeX公式手册(全网最全)

参考维基百科的数学公式教程
参考Cmd Markdown 公式指导手册

本文为 MathJax 在 Markdown 环境下的语法指引。

如何插入公式#

𝐿𝐴𝑇𝐸𝑋LATEX 的数学公式有两种:行中公式和独立公式(行间公式)。行中公式放在文中与其它文字混编,独立公式单独成行。

行中公式可以用如下方法表示:

1
$ 数学公式 $

独立公式可以用如下方法表示:

1
$$ 数学公式 $$

函数、符号及特殊字符#

声调 / 变音符号#

1
\dot{a}, \ddot{a}, \acute{a}, \grave{a}

𝑎˙,𝑎¨,𝑎́ ,𝑎̀ a˙,a¨,a´,a`

1
\check{a}, \breve{a}, \tilde{a}, \bar{a}

𝑎ˇ,𝑎˘,𝑎̃ ,𝑎¯aˇ,a˘,a~,a¯

1
\hat{a}, \widehat{a}, \vec{a}

𝑎̂ ,𝑎ˆ,𝑎⃗ a^,a^,a→

标准函数#

指数

1
\exp_a b = a^b, \exp b = e^b, 10^m

exp𝑎𝑏=𝑎𝑏,exp𝑏=𝑒𝑏,10𝑚expa⁡b=ab,exp⁡b=eb,10m

对数

1
\ln c, \lg d = \log e, \log_{10} f

ln𝑐,lg𝑑=log𝑒,log10𝑓ln⁡c,lg⁡d=log⁡e,log10⁡f

三角函数

1
\sin a, \cos b, \tan c, \cot d, \sec e, \csc f

sin𝑎,cos𝑏,tan𝑐,cot𝑑,sec𝑒,csc𝑓sin⁡a,cos⁡b,tan⁡c,cot⁡d,sec⁡e,csc⁡f

1
\arcsin a, \arccos b, \arctan c

arcsin𝑎,arccos𝑏,arctan𝑐arcsin⁡a,arccos⁡b,arctan⁡c

1
\arccot d, \arcsec e, \arccsc f

arccot𝑑,arcsec𝑒,arccsc𝑓arccot⁡d,arcsec⁡e,arccsc⁡f

1
\sinh a, \cosh b, \tanh c, \coth d

sinh𝑎,cosh𝑏,tanh𝑐,coth𝑑sinh⁡a,cosh⁡b,tanh⁡c,coth⁡d

1
\operatorname{sh}k, \operatorname{ch}l, \operatorname{th}m, \operatorname{coth}n

sh𝑘,ch𝑙,th𝑚,coth𝑛sh⁡k,ch⁡l,th⁡m,coth⁡n

1
\operatorname{argsh}o, \operatorname{argch}p, \operatorname{argth}q

argsh𝑜,argch𝑝,argth𝑞argsh⁡o,argch⁡p,argth⁡q

符号函数,绝对值

1
\sgn r, \left\vert s \right\vert

sgn𝑟,|𝑠|sgn⁡r,|s|

最大值,最小值

1
\min(x,y), \max(x,y)

min(𝑥,𝑦),max(𝑥,𝑦)min(x,y),max(x,y)

界限,极限#

1
\min x, \max y, \inf s, \sup t

min𝑥,max𝑦,inf𝑠,sup𝑡minx,maxy,infs,supt

1
\lim u, \liminf v, \limsup w

lim𝑢,liminf𝑣,limsup𝑤limu,lim infv,lim supw

1
\lim_{x \to \infty} \frac{1}{n(n+1)}

lim𝑥→∞1𝑛(𝑛+1)limx→∞1n(n+1)

1
\dim p, \deg q, \det m, \ker\phi

dim𝑝,deg𝑞,det𝑚,ker𝜙dim⁡p,deg⁡q,detm,ker⁡ϕ

投射#

1
\Pr j, \hom l, \lVert z \rVert, \arg z

Pr𝑗,hom𝑙,‖𝑧‖,arg𝑧Prj,hom⁡l,‖z‖,arg⁡z

微分及导数#

1
dt, \mathrm{d}t, \partial t, \nabla\psi

𝑑𝑡,d𝑡,∂𝑡,∇𝜓dt,dt,∂t,∇ψ

1
dy/dx, \mathrm{d}y/\mathrm{d}x, \frac{dy}{dx}, \frac{\mathrm{d}y}{\mathrm{d}x}, \frac{\partial^2}{\partial x_1\partial x_2}y

𝑑𝑦/𝑑𝑥,d𝑦/d𝑥,𝑑𝑦𝑑𝑥,d𝑦d𝑥,∂2∂𝑥1∂𝑥2𝑦dy/dx,dy/dx,dydx,dydx,∂2∂x1∂x2y

1
\prime, \backprime, f^\prime, f', f'', f^{(3)}, \dot y, \ddot y

′,‵,𝑓′,𝑓′,𝑓″,𝑓(3),𝑦˙,𝑦¨′,‵,f′,f′,f″,f(3),y˙,y¨

类字母符号及常数#

1
\infty, \aleph, \complement, \backepsilon, \eth, \Finv, \hbar

∞,ℵ,∁,∍,ð,Ⅎ,ℏ∞,ℵ,∁,∍,ð,Ⅎ,ℏ

1
\Im, \imath, \jmath, \Bbbk, \ell, \mho, \wp, \Re, \circledS

ℑ,ı,ȷ,𝕜,ℓ,℧,℘,ℜ,Ⓢℑ,ı,ȷ,k,ℓ,℧,℘,ℜ,Ⓢ

模运算#

1
s_k \equiv 0 \pmod{m}

𝑠𝑘≡0(mod𝑚)sk≡0(modm)

1
a \bmod b

𝑎mod𝑏amodb

1
\gcd(m, n), \operatorname{lcm}(m, n)

gcd(𝑚,𝑛),lcm(𝑚,𝑛)gcd(m,n),lcm⁡(m,n)

1
\mid, \nmid, \shortmid, \nshortmid

∣,∤,∣,∤∣,∤,∣,∤

根号#

1
\surd, \sqrt{2}, \sqrt[n]{}, \sqrt[3]{\frac{x^3+y^3}{2}}

√,2‾√,√𝑛,𝑥3+𝑦32‾‾‾‾‾‾‾‾√3√,2,n,x3+y323

运算符#

1
+, -, \pm, \mp, \dotplus

+,−,±,∓,∔+,−,±,∓,∔

1
\times, \div, \divideontimes, /, \backslash

×,÷,⋇,/,∖×,÷,⋇,/,∖

1
\cdot, * \ast, \star, \circ, \bullet

⋅,∗∗,⋆,∘,∙⋅,∗∗,⋆,∘,∙

1
\boxplus, \boxminus, \boxtimes, \boxdot

⊞,⊟,⊠,⊡⊞,⊟,⊠,⊡

1
\oplus, \ominus, \otimes, \oslash, \odot

⊕,⊖,⊗,⊘,⊙⊕,⊖,⊗,⊘,⊙

1
\circleddash, \circledcirc, \circledast

⊝,⊚,⊛⊝,⊚,⊛

1
\bigoplus, \bigotimes, \bigodot

⨁,⨂,⨀⨁,⨂,⨀

集合#

1
\{ \}, \O \empty \emptyset, \varnothing

{},∅∅∅,∅{},∅∅∅,∅

1
\in, \notin \not\in, \ni, \not\ni

∈,∉∉,∋,∌∈,∉∉,∋,∌

1
\cap, \Cap, \sqcap, \bigcap

∩,⋒,⊓,⋂∩,⋒,⊓,⋂

1
\cup, \Cup, \sqcup, \bigcup, \bigsqcup, \uplus, \biguplus

∪,⋓,⊔,⋃,⨆,⊎,⨄∪,⋓,⊔,⋃,⨆,⊎,⨄

1
\setminus, \smallsetminus, \times

∖,∖,×∖,∖,×

1
\subset, \Subset, \sqsubset

⊂,⋐,⊏⊂,⋐,⊏

1
\supset, \Supset, \sqsupset

⊃,⋑,⊐⊃,⋑,⊐

1
\subseteq, \nsubseteq, \subsetneq, \varsubsetneq, \sqsubseteq

⊆,⊈,⊊,⊊,⊑⊆,⊈,⊊,⊊,⊑

1
\supseteq, \nsupseteq, \supsetneq, \varsupsetneq, \sqsupseteq

⊇,⊉,⊋,⊋,⊒⊇,⊉,⊋,⊋,⊒

1
\subseteqq, \nsubseteqq, \subsetneqq, \varsubsetneqq

⫅,,⫋,⫋⫅,⊈,⫋,⫋

1
\supseteqq, \nsupseteqq, \supsetneqq, \varsupsetneqq

⫆,,⫌,⫌⫆,⊉,⫌,⫌

关系符号#

1
=, \ne, \neq, \equiv, \not\equiv

=,≠,≠,≡,≢=,≠,≠,≡,≢

1
\doteq, \doteqdot,` `\overset{\underset{\mathrm{def}}{}}{=},` `:=

≐,≑,=def,:=≐,≑,=def,:=

1
\sim, \nsim, \backsim, \thicksim, \simeq, \backsimeq, \eqsim, \cong, \ncong

∼,≁,∽,∼,≃,⋍,≂,≅,≆∼,≁,∽,∼,≃,⋍,≂,≅,≆

1
\approx, \thickapprox, \approxeq, \asymp, \propto, \varpropto

≈,≈,≊,≍,∝,∝≈,≈,≊,≍,∝,∝

1
<, \nless, \ll, \not\ll, \lll, \not\lll, \lessdot

<,≮,≪,≪̸,⋘,⋘̸,⋖<,≮,≪,≪̸,⋘,⋘̸,⋖

1
>, \ngtr, \gg, \not\gg, \ggg, \not\ggg, \gtrdot

>,≯,≫,≫̸,⋙,⋙̸,⋗>,≯,≫,≫̸,⋙,⋙̸,⋗

1
\le, \leq, \lneq, \leqq, \nleq, \nleqq, \lneqq, \lvertneqq

≤,≤,⪇,≦,≰,,≨,≨≤,≤,⪇,≦,≰,≰,≨,≨

1
\ge, \geq, \gneq, \geqq, \ngeq, \ngeqq, \gneqq, \gvertneqq

≥,≥,⪈,≧,≱,,≩,≩≥,≥,⪈,≧,≱,≱,≩,≩

1
\lessgtr, \lesseqgtr, \lesseqqgtr, \gtrless, \gtreqless, \gtreqqless

≶,⋚,⪋,≷,⋛,⪌≶,⋚,⪋,≷,⋛,⪌

1
\leqslant, \nleqslant, \eqslantless

⩽,,⪕⩽,⪇,⪕

1
\geqslant, \ngeqslant, \eqslantgtr

⩾,,⪖⩾,⪈,⪖

1
\lesssim, \lnsim, \lessapprox, \lnapprox

≲,⋦,⪅,⪉≲,⋦,⪅,⪉

1
\gtrsim, \gnsim, \gtrapprox, \gnapprox

≳,⋧,⪆,⪊≳,⋧,⪆,⪊

1
\prec, \nprec, \preceq, \npreceq, \precneqq

≺,⊀,⪯,,⪵≺,⊀,⪯,⋠,⪵

1
\succ, \nsucc, \succeq, \nsucceq, \succneqq

≻,⊁,⪰,,⪶≻,⊁,⪰,⋡,⪶

1
\preccurlyeq, \curlyeqprec

≼,⋞≼,⋞

1
\succcurlyeq, \curlyeqsucc

≽,⋟≽,⋟

1
\precsim, \precnsim, \precapprox, \precnapprox

≾,⋨,⪷,⪹≾,⋨,⪷,⪹

1
\succsim, \succnsim, \succapprox, \succnapprox

≿,⋩,⪸,⪺≿,⋩,⪸,⪺

几何符号#

1
\parallel, \nparallel, \shortparallel, \nshortparallel

∥,∦,∥,∦∥,∦,∥,∦

1
\perp, \angle, \sphericalangle, \measuredangle, 45^\circ

⊥,∠,∢,∡,45∘⊥,∠,∢,∡,45∘

1
\Box, \blacksquare, \diamond, \Diamond \lozenge, \blacklozenge, \bigstar

◻,◼,⋄,◊◊,⧫,★◻,◼,⋄,◊◊,⧫,★

1
\bigcirc, \triangle, \bigtriangleup, \bigtriangledown

◯,△,△,▽◯,△,△,▽

1
\vartriangle, \triangledown

▵,▿△,▽

1
\blacktriangle, \blacktriangledown, \blacktriangleleft, \blacktriangleright

▴,▾,◂,▸▴,▾,◂,▸

逻辑符号#

1
\forall, \exists, \nexists

∀,∃,∄∀,∃,∄

1
\therefore, \because, \And

∴,∵,&∴,∵,&

1
\or \lor \vee, \curlyvee, \bigvee

∨,∨,∨,⋎,⋁∨,∨,∨,⋎,⋁

1
\and \land \wedge, \curlywedge, \bigwedge

∧,∧,∧,⋏,⋀∧,∧,∧,⋏,⋀

1
2
\bar{q}, \bar{abc}, \overline{q}, \overline{abc},
\lnot \neg, \not\operatorname{R}, \bot, \top

𝑞¯,𝑎𝑏𝑐¯,𝑞⎯⎯⎯,𝑎𝑏𝑐⎯⎯⎯⎯⎯⎯⎯⎯,q¯,abc¯,q¯,abc¯,

¬¬,⧸R,⊥,⊤¬¬,⧸R,⊥,⊤

1
\vdash \dashv, \vDash, \Vdash, \models

⊢,⊣,⊨,⊩,⊨⊢,⊣,⊨,⊩,⊨

1
\Vvdash \nvdash \nVdash \nvDash \nVDash

⊪,⊬,⊮,⊭,⊯⊪,⊬,⊮,⊭,⊯

1
\ulcorner \urcorner \llcorner \lrcorner

⌜⌝⌞⌟⌜⌝⌞⌟

箭头#

1
\Rrightarrow, \Lleftarrow

⇛,⇚⇛,⇚

1
\Rightarrow, \nRightarrow, \Longrightarrow \implies

⇒,⇏,⟹,⟹⇒,⇏,⟹,⟹

1
\Leftarrow, \nLeftarrow, \Longleftarrow

⇐,⇍,⟸⇐,⇍,⟸

1
\Leftrightarrow, \nLeftrightarrow, \Longleftrightarrow \iff

⇔,⇎,⟺⟺⇔,⇎,⟺⟺

1
\Uparrow, \Downarrow, \Updownarrow

⇑,⇓,⇕⇑,⇓,⇕

1
\rightarrow \to, \nrightarrow, \longrightarrow

→→,↛,⟶→→,↛,⟶

1
\leftarrow \gets, \nleftarrow, \longleftarrow

←←,↚,⟵←←,↚,⟵

1
\leftrightarrow, \nleftrightarrow, \longleftrightarrow

↔,↮,⟷↔,↮,⟷

1
\uparrow, \downarrow, \updownarrow

↑,↓,↕↑,↓,↕

1
\nearrow, \swarrow, \nwarrow, \searrow

↗,↙,↖,↘↗,↙,↖,↘

1
\mapsto, \longmapsto

↦,⟼↦,⟼

1
\rightharpoonup \rightharpoondown \leftharpoonup \leftharpoondown \upharpoonleft \upharpoonright \downharpoonleft \downharpoonright \rightleftharpoons \leftrightharpoons

⇀,⇁,↼,↽,↿,↾,⇃,⇂,⇌,⇋⇀,⇁,↼,↽,↿,↾,⇃,⇂,⇌,⇋

1
\curvearrowleft \circlearrowleft \Lsh \upuparrows \rightrightarrows \rightleftarrows \rightarrowtail \looparrowright

↶,↺,↰,⇈,⇉,⇄,↣,↬↶,↺,↰,⇈,⇉,⇄,↣,↬

1
\curvearrowright \circlearrowright \Rsh \downdownarrows \leftleftarrows \leftrightarrows \leftarrowtail \looparrowleft

↷,↻,↱,⇊,⇇,⇆,↢,↫↷,↻,↱,⇊,⇇,⇆,↢,↫

1
\hookrightarrow \hookleftarrow \multimap \leftrightsquigarrow \rightsquigarrow \twoheadrightarrow \twoheadleftarrow

↪,↩,⊸,↭,⇝,↠,↞↪,↩,⊸,↭,⇝,↠,↞

特殊符号#

省略号:数学公式中常见的省略号有两种,\ldots 表示与文本底线对齐的省略号,\cdots 表示与文本中线对齐的省略号。

1
\amalg \% \dagger \ddagger \ldots \cdots

⨿%†‡…⋯⨿%†‡…⋯

1
\smile \frown \wr \triangleleft \triangleright

⌣⌢≀◃▹⌣⌢≀◃▹

1
\diamondsuit, \heartsuit, \clubsuit, \spadesuit, \Game, \flat, \natural, \sharp

♢,♡,♣,♠,⅁,♭,♮,♯♢,♡,♣,♠,⅁,♭,♮,♯

未分类#

1
\diagup \diagdown \centerdot \ltimes \rtimes \leftthreetimes \rightthreetimes

╱,╲,⋅,⋉,⋊,⋋,⋌╱,╲,⋅,⋉,⋊,⋋,⋌

1
\eqcirc \circeq \triangleq \bumpeq \Bumpeq \doteqdot \risingdotseq \fallingdotseq

≖,≗,≜,≏,≎,≑,≓,≒≖,≗,≜,≏,≎,≑,≓,≒

1
\intercal \barwedge \veebar \doublebarwedge \between \pitchfork

⊺,⊼,⊻,⩞,≬,⋔⊺,⊼,⊻,⩞,≬,⋔

1
\vartriangleleft \ntriangleleft \vartriangleright \ntriangleright

⊲,⋪,⊳,⋫⊲,⋪,⊳,⋫

1
\trianglelefteq \ntrianglelefteq \trianglerighteq \ntrianglerighteq

⊴,⋬,⊵,⋭⊴,⋬,⊵,⋭

关于这些符号的更多语义,参阅 TeX Cookbook 的简述。

上标、下标及积分等#

功能|语法|效果

^ 表示上标, _ 表示下标。如果上下标的内容多于一个字符,需要用 {} 将这些内容括成一个整体。上下标可以嵌套,也可以同时使用。

上标

1
a^2

𝑎2a2

下标

1
a_2

𝑎2a2

组合

1
a^{2+2}

𝑎2+2a2+2

1
a_{i,j}

𝑎𝑖,𝑗ai,j

结合上下标

1
x_2^3

𝑥32x23

前置上下标

1
{}_1^2\!X_3^4

21𝑋4312X34

导数(HTML

1
x'

𝑥′x′

导数(PNG

1
x^\prime

𝑥′x′

导数(错误

1
x\prime

𝑥′x′

导数点

1
\dot{x}

𝑥˙x˙

1
\ddot{y}

𝑦¨y¨

向量

\vec{c}(只有一个字母)

𝑐⃗ c→

1
\overleftarrow{a b}

𝑎𝑏←−ab←

1
\overrightarrow{c d}

𝑐𝑑−→cd→

1
\overleftrightarrow{a b}

𝑎𝑏←→ab↔

1
\widehat{e f g}

𝑒𝑓𝑔ˆefg^

上弧

(注: 正确应该用 \overarc,但在这里行不通。要用建议的语法作为解决办法。)(使用 overarc 时需要引入 {arcs} 包。)

1
\overset{\frown} {AB}

𝐴𝐵⌢AB⌢

上划线

1
\overline{h i j}

ℎ𝑖𝑗⎯⎯⎯⎯⎯⎯⎯hij¯

下划线

1
\underline{k l m}

𝑘𝑙𝑚⎯⎯⎯⎯⎯⎯klm_

上括号

1
\overbrace{1+2+\cdots+100}

1+2+⋯+1001+2+⋯+100⏞

1
\begin{matrix} 5050 \\ \overbrace{ 1+2+\cdots+100 } \end{matrix}

50501+2+⋯+10050501+2+⋯+100⏞

下括号

1
\underbrace{a+b+\cdots+z}

𝑎+𝑏+⋯+𝑧a+b+⋯+z⏟

1
\begin{matrix} \underbrace{ a+b+\cdots+z } \\ 26 \end{matrix}

𝑎+𝑏+⋯+𝑧26a+b+⋯+z⏟26

求和(累加)

1
\sum_{k=1}^N k^2

∑𝑘=1𝑁𝑘2∑k=1Nk2

1
\begin{matrix} \sum_{k=1}^N k^2 \end{matrix}

∑𝑁𝑘=1𝑘2∑k=1Nk2

求积(累乘)

1
\prod_{i=1}^N x_i

∏𝑖=1𝑁𝑥𝑖∏i=1Nxi

1
\begin{matrix} \prod_{i=1}^N x_i \end{matrix}

∏𝑁𝑖=1𝑥𝑖∏i=1Nxi

上积

1
\coprod_{i=1}^N x_i

∐𝑖=1𝑁𝑥𝑖∐i=1Nxi

1
\begin{matrix} \coprod_{i=1}^N x_i \end{matrix}

∐𝑁𝑖=1𝑥𝑖∐i=1Nxi

极限

1
\lim_{n \to \infty}x_n

lim𝑛→∞𝑥𝑛limn→∞xn

1
\begin{matrix} \lim_{n \to \infty}x_n \end{matrix}

lim𝑛→∞𝑥𝑛limn→∞xn

积分

1
\int_{-N}^{N} e^x\, {\rm d}x

∫𝑁−𝑁𝑒𝑥d𝑥∫−NNexdx

本例中 \,{\rm d} 部分可省略,但建议加入,能使式子更美观。{\rm d}可以用\mathrm{d}等价替换。

\begin{matrix} \int_{-N}^{N} e^x\, \mathrm{d}x \end{matrix}(矩阵中积分符号变小)

∫𝑁−𝑁𝑒𝑥d𝑥∫−NNexdx

双重积分

1
\iint_{D}^{W} \, \mathrm{d}x\,\mathrm{d}y

∬𝑊𝐷d𝑥d𝑦∬DWdxdy

三重积分

1
\iiint_{E}^{V} \, \mathrm{d}x\,\mathrm{d}y\,\mathrm{d}z

∭𝑉𝐸d𝑥d𝑦d𝑧∭EVdxdydz

闭合的曲线、曲面积分

1
\oint_{C} x^3\, \mathrm{d}x + 4y^2\, \mathrm{d}y

∮𝐶𝑥3d𝑥+4𝑦2d𝑦∮C⁡x3dx+4y2dy

交集

1
\bigcap_1^{n} p

⋂1𝑛𝑝⋂1np

并集

1
\bigcup_1^{k} p

⋃1𝑘𝑝⋃1kp

分数#

通常使用 \frac {分子} {分母} 命令产生一个分数,分数可嵌套。
便捷情况可直接输入 \frac ab 来快速生成一个 𝑎𝑏ab 。
如果分式很复杂,亦可使用 分子 \over 分母 命令,此时分数仅有一层。

功能|语法|效果

分数

1
\frac{2}{4}=0.5

24=0.524=0.5

小型分数

1
\tfrac{2}{4} = 0.5

24=0.524=0.5

连分式(大型嵌套分式)

1
\cfrac{2}{c + \cfrac{2}{d + \cfrac{2}{4}}} = a

2𝑐+2𝑑+24=𝑎2c+2d+24=a

大型不嵌套分式

1
\dfrac{2}{4} = 0.5 \qquad \dfrac{2}{c + \dfrac{2}{d + \dfrac{2}{4}}} = a

24=0.52𝑐+2𝑑+24=𝑎24=0.52c+2d+24=a

二项式系数

1
\dbinom{n}{r}=\binom{n}{n-r}=\mathrm{C}_n^r=\mathrm{C}_n^{n-r}

(𝑛𝑟)=(𝑛𝑛−𝑟)=C𝑟𝑛=C𝑛−𝑟𝑛(nr)=(nn−r)=Cnr=Cnn−r

小型二项式系数

1
\tbinom{n}{r}=\tbinom{n}{n-r}=\mathrm{C}_n^r=\mathrm{C}_n^{n-r}

(𝑛𝑟)=(𝑛𝑛−𝑟)=C𝑟𝑛=C𝑛−𝑟𝑛(nr)=(nn−r)=Cnr=Cnn−r

大型二项式系数

1
\binom{n}{r}=\dbinom{n}{n-r}=\mathrm{C}_n^r=\mathrm{C}_n^{n-r}

(𝑛𝑟)=(𝑛𝑛−𝑟)=C𝑟𝑛=C𝑛−𝑟𝑛(nr)=(nn−r)=Cnr=Cnn−r

在以e为底的指数函数、极限和积分中尽量不要使用 \frac 符号:它会使整段函数看起来很怪,而且可能产生歧义。也正是因此它在专业数学排版中几乎从不出现。
横着写这些分式,中间使用斜线间隔 / (用斜线代替分数线)。

  • 例子:
1
2
3
4
5
6
Copy\begin{array}{cc}
\mathrm{Bad} & \mathrm{Better} \\
\hline \\
e^{i\frac{\pi}2} \quad e^{\frac{i\pi}2}& e^{i\pi/2} \\
\int_{-\frac\pi2}^\frac\pi2 \sin x\,dx & \int_{-\pi/2}^{\pi/2}\sin x\,dx \\
\end{array}
  • 显示:

Bad𝑒𝑖𝜋2𝑒𝑖𝜋2∫𝜋2−𝜋2sin𝑥𝑑𝑥Better𝑒𝑖𝜋/2∫𝜋/2−𝜋/2sin𝑥𝑑𝑥BadBettereiπ2eiπ2eiπ/2∫−π2π2sin⁡xdx∫−π/2π/2sin⁡xdx

矩阵、条件表达式、方程组#

语法:

1
2
3
Copy\begin{类型}
公式内容
\end{类型}

类型可以是:矩阵 matrix pmatrix bmatrix Bmatrix vmatrix Vmatrix、条件表达式 cases、多行对齐方程式 aligned、数组 array

在公式内容中:在每一行中插入 & 来指定需要对齐的内容,在每行结尾处使用 \\ 换行

无框矩阵#

在开头使用 begin{matrix},在结尾使用 end{matrix},在中间插入矩阵元素,每个元素之间插入 & ,并在每行结尾处使用 \\

1
2
3
4
Copy\begin{matrix}
x & y \\
z & v
\end{matrix}

𝑥𝑧𝑦𝑣xyzv

有框矩阵#

在开头将 matrix 替换为 pmatrix bmatrix Bmatrix vmatrix Vmatrix

1
2
3
4
Copy\begin{vmatrix}
x & y \\
z & v
\end{vmatrix}

∣∣∣𝑥𝑧𝑦𝑣∣∣∣|xyzv|

1
2
3
4
Copy\begin{Vmatrix}
x & y \\
z & v
\end{Vmatrix}

‖‖‖𝑥𝑧𝑦𝑣‖‖‖‖xyzv‖

使用 \cdots ⋯⋯ , \ddots ⋱⋱ , \vdots ⋮⋮ 来输入省略符号

1
2
3
4
5
Copy\begin{bmatrix}
0 & \cdots & 0 \\
\vdots & \ddots & \vdots \\
0 & \cdots & 0
\end{bmatrix}

⎡⎣⎢⎢⎢0⋮0⋯⋱⋯0⋮0⎤⎦⎥⎥⎥[0⋯0⋮⋱⋮0⋯0]

1
2
3
4
Copy\begin{Bmatrix}
x & y \\
z & v
\end{Bmatrix}

{𝑥𝑧𝑦𝑣}{xyzv}

1
2
3
4
Copy\begin{pmatrix}
x & y \\
z & v
\end{pmatrix}

(𝑥𝑧𝑦𝑣)(xyzv)

条件表达式#

1
2
3
4
5
Copyf(n) =
\begin{cases}
n/2, & \text{if }n\text{ is even} \\
3n+1, & \text{if }n\text{ is odd}
\end{cases}

𝑓(𝑛)={𝑛/2,3𝑛+1,if 𝑛 is evenif 𝑛 is oddf(n)={n/2,if n is even3n+1,if n is odd

多行等式、同余式#

人们经常想要一列整齐且居中的方程式序列。使用 \begin{aligned}…\end{aligned}

1
2
3
4
Copy\begin{aligned}
f(x) & = (m+n)^2 \\
& = m^2+2mn+n^2 \\
\end{aligned}

𝑓(𝑥)=(𝑚+𝑛)2=𝑚2+2𝑚𝑛+𝑛2f(x)=(m+n)2=m2+2mn+n2

1
2
3
4
5
6
7
8
Copybegin{aligned}
3^{6n+3}+4^{6n+3}
& \equiv (3^3)^{2n+1}+(4^3)^{2n+1}\\
& \equiv 27^{2n+1}+64^{2n+1}\\
& \equiv 27^{2n+1}+(-27)^{2n+1}\\
& \equiv 27^{2n+1}-27^{2n+1}\\
& \equiv 0 \pmod{91}\\
\end{aligned}

36𝑛+3+46𝑛+3≡(33)2𝑛+1+(43)2𝑛+1≡272𝑛+1+642𝑛+1≡272𝑛+1+(−27)2𝑛+1≡272𝑛+1−272𝑛+1≡0(mod91)36n+3+46n+3≡(33)2n+1+(43)2n+1≡272n+1+642n+1≡272n+1+(−27)2n+1≡272n+1−272n+1≡0(mod91)

1
2
3
4
5
Copy\begin{alignedat}{3}
f(x) & = (m-n)^2 \\
f(x) & = (-m+n)^2 \\
& = m^2-2mn+n^2 \\
\end{alignedat}

𝑓(𝑥)𝑓(𝑥)=(𝑚−𝑛)2=(−𝑚+𝑛)2=𝑚2−2𝑚𝑛+𝑛2f(x)=(m−n)2f(x)=(−m+n)2=m2−2mn+n2

方程组#

1
2
3
4
5
Copy\begin{cases}
3x + 5y + z \\
7x - 2y + 4z \\
-6x + 3y + 2z
\end{cases}

⎧⎩⎨⎪⎪3𝑥+5𝑦+𝑧7𝑥−2𝑦+4𝑧−6𝑥+3𝑦+2𝑧{3x+5y+z7x−2y+4z−6x+3y+2z

1
2
3
4
5
Copy\left\{\begin{aligned}
3x + 5y + z \\
7x - 2y + 4z \\
-6x + 3y + 2z
\end{aligned}\right.

⎧⎩⎨⎪⎪3𝑥+5𝑦+𝑧7𝑥−2𝑦+4𝑧−6𝑥+3𝑦+2𝑧{3x+5y+z7x−2y+4z−6x+3y+2z

数组与表格#

通常,一个格式化后的表格比单纯的文字或排版后的文字更具有可读性。数组和表格均以 \begin{array} 开头,并在其后定义列数及每一列的文本对齐属性,c l r 分别代表居中、左对齐及右对齐。若需要插入垂直分割线,在定义式中插入 | ,若要插入水平分割线,在下一行输入前插入 \hline 。与矩阵相似,每行元素间均须要插入 & ,每行元素以 \\ 结尾,最后以 \end{array} 结束数组。

  • 例子:
1
2
3
4
5
6
7
Copy\begin{array}{c|lcr}
n & \text{左对齐} & \text{居中对齐} & \text{右对齐} \\
\hline
1 & 0.24 & 1 & 125 \\
2 & -1 & 189 & -8 \\
3 & -20 & 2000 & 1+10i
\end{array}
  • 显示:

    𝑛123左对齐0.24−1−20居中对齐11892000右对齐125−81+10𝑖n左对齐居中对齐右对齐10.2411252−1189−83−2020001+10i

  • 例子:
1
2
3
4
Copy\begin{array}{lcl}
z & = & a \\
f(x,y,z) & = & x + y + z
\end{array}
  • 显示:

𝑧𝑓(𝑥,𝑦,𝑧)==𝑎𝑥+𝑦+𝑧z=af(x,y,z)=x+y+z

  • 例子:
1
2
3
4
Copy\begin{array}{lcr}
z & = & a \\
f(x,y,z) & = & x + y + z
\end{array}
  • 显示:

𝑧𝑓(𝑥,𝑦,𝑧)==𝑎𝑥+𝑦+𝑧z=af(x,y,z)=x+y+z

  • 例子:
1
2
3
4
5
6
7
8
Copy\begin{array}{ccc}
a & b & S \\
\hline
0&0&1\\
0&1&1\\
1&0&1\\
1&1&0\\
\end{array}
  • 显示:

𝑎0011𝑏0101𝑆1110abS001011101110

嵌套数组或表格#

多个数组/表格可 互相嵌套 并组成一组数组/一组表格。
使用嵌套前必须声明 $$ 符号。

  • 例子:
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
Copy% outer vertical array of arrays 外层垂直表格
\begin{array}{c}
% inner horizontal array of arrays 内层水平表格
\begin{array}{cc}
% inner array of minimum values 内层"最小值"数组
\begin{array}{c|cccc}
\text{min} & 0 & 1 & 2 & 3\\
\hline
0 & 0 & 0 & 0 & 0\\
1 & 0 & 1 & 1 & 1\\
2 & 0 & 1 & 2 & 2\\
3 & 0 & 1 & 2 & 3
\end{array}
&
% inner array of maximum values 内层"最大值"数组
\begin{array}{c|cccc}
\text{max}&0&1&2&3\\
\hline
0 & 0 & 1 & 2 & 3\\
1 & 1 & 1 & 2 & 3\\
2 & 2 & 2 & 2 & 3\\
3 & 3 & 3 & 3 & 3
\end{array}
\end{array}
% 内层第一行表格组结束
\\
% inner array of delta values 内层第二行Delta值数组
\begin{array}{c|cccc}
\Delta&0&1&2&3\\
\hline
0 & 0 & 1 & 2 & 3\\
1 & 1 & 0 & 1 & 2\\
2 & 2 & 1 & 0 & 1\\
3 & 3 & 2 & 1 & 0
\end{array}
% 内层第二行表格组结束
\end{array}
  • 显示:

min012300000101112012230123max012300123111232222333333Δ012300123110122210133210min012300000101112012230123max012300123111232222333333Δ012300123110122210133210

用数组实现带分割符号的矩阵#

  • 例子:
1
2
3
4
5
6
7
8
Copy$$
\left[
\begin{array}{cc|c}
1&2&3\\
4&5&6
\end{array}
\right]
$$
  • 显示:

[142536][123456]

其中 cc|c 代表在一个三列矩阵中的第二和第三列之间插入分割线。

字体#

希腊字母#

输入 \小写希腊字母英文全称\首字母大写希腊字母英文全称 来分别输入小写和大写希腊字母。

1
\Alpha \Beta \Gamma \Delta \Epsilon \Zeta \Eta \Theta

ABΓΔEZHΘABΓΔEZHΘ

1
\Iota \Kappa \Lambda \Mu \Nu \Xi \Omicron \Pi

IKΛMNOΞΠIKΛMNOΞΠ

1
\Rho \Sigma \Tau \Upsilon \Phi \Chi \Psi \Omega

PΣTΥΦXΨΩPΣTΥΦXΨΩ

1
\alpha \beta \gamma \delta \epsilon \zeta \eta \theta

𝛼𝛽𝛾𝛿𝜖𝜁𝜂𝜃αβγδϵζηθ

1
\iota \kappa \lambda \mu \nu \omicron \xi \pi

𝜄𝜅𝜆𝜇𝜈o𝜉𝜋ικλμνoξπ

1
\rho \sigma \tau \upsilon \phi \chi \psi \omega

𝜌𝜎𝜏𝜐𝜙𝜒𝜓𝜔ρστυϕχψω

部分字母有变量专用形式,以 \var- 开头。

1
\varepsilon \digamma \varkappa \varpi

𝜀ϝ𝜘𝜛εϝϰϖ

1
\varrho \varsigma \vartheta \varphi

𝜚𝜍𝜗𝜑ϱςϑφ

希伯来符号#

1
\aleph \beth \gimel \daleth

ℵℶℷℸℵℶℷℸ

部分字体的简称#

若要对公式的某一部分字符进行字体转换,可以用 {\字体 {需转换的部分字符}} 命令,其中 \字体 部分可以参照下表选择合适的字体。一般情况下,公式默认为意大利体 𝑖𝑡𝑎𝑙𝑖𝑐italic 。

输入 说明 显示 输入 说明 显示
\rm 罗马体 SampleSample \cal 花体 SAMPLE
\it 意大利体 SampleSample \Bbb 黑板粗体 𝕊𝔸𝕄ℙ𝕃𝔼SAMPLE
\bf 粗体 𝐒**𝐚𝐦𝐩𝐥𝐞**Sample \mit 数学斜体 𝑆𝐴𝑀𝑃𝐿𝐸SAMPLE
\sf 等线体 𝖲𝖺𝗆𝗉𝗅𝖾Sample \scr 手写体 𝒮𝒜ℳ𝒫ℒℰSAMPLE
\tt 打字机体 𝚂𝚊𝚖𝚙𝚕𝚎Sample \frak 旧德式字体 𝔖𝔞𝔪𝔭𝔩𝔢Sample

所有字体#

黑板报粗体

1
\mathbb{ABCDEFGHI}

𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀ABCDEFGHI

1
\mathbb{JKLMNOPQR}

𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝJKLMNOPQR

1
\mathbb{STUVWXYZ}

𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤSTUVWXYZ

粗体

1
\mathbf{ABCDEFGHI}

𝐀**𝐁𝐂𝐃𝐄𝐅𝐆𝐇**𝐈ABCDEFGHI

1
\mathbf{JKLMNOPQR}

𝐉**𝐊𝐋𝐌𝐍𝐎𝐏𝐐**𝐑JKLMNOPQR

1
\mathbf{STUVWXYZ}

𝐒**𝐓𝐔𝐕𝐖𝐗𝐘𝐙**STUVWXYZ

1
\mathbf{abcdefghijklm}

𝐚**𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥**𝐦abcdefghijklm

1
\mathbf{nopqrstuvwxyz}

𝐧**𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲**𝐳nopqrstuvwxyz

1
\mathbf{0123456789}

01234567890123456789

粗体希腊字母

1
\boldsymbol{\Alpha\Beta\Gamma\Delta\Epsilon\Zeta\Eta\Theta}

𝐀**𝐁𝚪𝚫𝐄𝐙𝐇𝚯**ABΓΔEZHΘ

1
\boldsymbol{\Iota\Kappa\Lambda\Mu\Nu\Xi\Pi\Rho}

𝐈**𝐊𝚲𝐌𝐍𝚵𝚷𝐏**IKΛMNΞΠP

1
\boldsymbol{\Sigma\Tau\Upsilon\Phi\Chi\Psi\Omega}

𝚺**𝐓𝚼𝚽𝐗𝚿**𝛀ΣTΥΦXΨΩ

1
\boldsymbol{\alpha\beta\gamma\delta\epsilon\zeta\eta\theta}

𝜶**𝜷𝜸𝜹𝝐𝜻𝜼𝜽**αβγδϵζηθ

1
\boldsymbol{\iota\kappa\lambda\mu\nu\xi\pi\rho}

𝜾**𝜿𝝀𝝁𝝂𝝃𝝅𝝆**ικλμνξπρ

1
\boldsymbol{\sigma\tau\upsilon\phi\chi\psi\omega}

𝝈**𝝉𝝊𝝓𝝌𝝍**𝝎στυϕχψω

1
\boldsymbol{\varepsilon\digamma\varkappa\varpi}

𝜺**ϝ𝝒𝝕**εϝϰϖ

1
\boldsymbol{\varrho\varsigma\vartheta\varphi}

𝝔**𝝇𝝑𝝋**ϱςϑφ

斜体(拉丁字母默认)

1
\mathit{0123456789}

01234567890123456789

斜体希腊字母(小写字母默认)

1
\mathit{\Alpha\Beta\Gamma\Delta\Epsilon\Zeta\Eta\Theta}

ABΓΔEZHΘABΓΔEZHΘ

1
\mathit{\Iota\Kappa\Lambda\Mu\Nu\Xi\Pi\Rho}

IKΛMNΞΠPIKΛMNΞΠP

1
\mathit{\Sigma\Tau\Upsilon\Phi\Chi\Psi\Omega}

ΣTΥΦXΨΩΣTΥΦXΨΩ

罗马体

1
\mathrm{ABCDEFGHI}

ABCDEFGHIABCDEFGHI

1
\mathrm{JKLMNOPQR}

JKLMNOPQRJKLMNOPQR

1
\mathrm{STUVWXYZ}

STUVWXYZSTUVWXYZ

1
\mathrm{abcdefghijklm}

abcdefghijklmabcdefghijklm

1
\mathrm{nopqrstuvwxyz}

nopqrstuvwxyznopqrstuvwxyz

1
\mathrm{0123456789}

01234567890123456789

无衬线体

1
\mathsf{ABCDEFGHI}

𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨ABCDEFGHI

1
\mathsf{JKLMNOPQR}

𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱JKLMNOPQR

1
\mathsf{STUVWXYZ}

𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹STUVWXYZ

1
\mathsf{abcdefghijklm}

𝖺𝖻𝖼𝖽𝖾𝖿𝗀𝗁𝗂𝗃𝗄𝗅𝗆abcdefghijklm

1
\mathsf{nopqrstuvwxyz}

𝗇𝗈𝗉𝗊𝗋𝗌𝗍𝗎𝗏𝗐𝗑𝗒𝗓nopqrstuvwxyz

1
\mathsf{0123456789}

𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫0123456789

无衬线体希腊字母(仅大写)

1
\mathsf{\Alpha \Beta \Gamma \Delta \Epsilon \Zeta \Eta \Theta}

ABEZHABΓΔEZHΘ

1
\mathsf{\Iota \Kappa \Lambda \Mu \Nu \Xi \Pi \Rho}

IKMNPIKΛMNΞΠP

1
\mathsf{\Sigma \Tau \Upsilon \Phi \Chi \Psi \Omega}

TXΣTΥΦXΨΩ

手写体 / 花体

1
\mathcal{ABCDEFGHI}

ABCDEFGHI

1
\mathcal{JKLMNOPQR}

JKLMNOPQR

1
\mathcal{STUVWXYZ}

STUVWXYZ

Fraktur 体

1
\mathfrak{ABCDEFGHI}

𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑABCDEFGHI

1
\mathfrak{JKLMNOPQR}

𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜJKLMNOPQR

1
\mathfrak{STUVWXYZ}

𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨSTUVWXYZ

1
\mathfrak{abcdefghijklm}

𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪abcdefghijklm

1
\mathfrak{nopqrstuvwxyz}

𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷nopqrstuvwxyz

1
\mathfrak{0123456789}

01234567890123456789

小型手写体

1
{\scriptstyle\text{abcdefghijklm}}

abcdefghijklmabcdefghijklm

混合字体#

特征|语法|渲染效果

斜体字符(忽略空格)

1
x y z

𝑥𝑦𝑧xyz

非斜体字符

1
\text{x y z}

x y zx y z

混合斜体(差)

1
\text{if} n \text{is even}

if𝑛is evenifnis even

混合斜体(好)

1
\text{if }n\text{ is even}

if 𝑛 is evenif n is even

混合斜体(替代品:~ 或者 \ 强制空格)

1
\text{if}~n\ \text{is even}

if 𝑛 is evenif n is even

注释文本#

使用 \text {文字} 来添加注释文本(注释文本不会被识别为公式,不用斜体显示)。\text {文字}中仍可以使用 $公式$ 插入其它公式。

  • 例子:
1
2
3
4
Copyf(n)= \begin{cases}
n/2, & \text {if $n$ is even} \\
3n+1, &\text{if $n$ is odd}
\end{cases}
  • 显示:

𝑓(𝑛)={𝑛/2,3𝑛+1,if 𝑛 is evenif 𝑛 is oddf(n)={n/2,if n is even3n+1,if n is odd

括号#

()[]| 表示符号本身,使用 \{\} 来表示 {}

功能|语法|显示

短括号

1
\frac{1}{2}

(12)(12)

长括号

1
\left(\frac{1}{2} \right)

(12)(12)

使用 \left\right 来创建自动匹配高度的 (圆括号),[方括号] 和 {花括号} 。

功能|语法|显示

圆括号,小括号

1
\left( \frac{a}{b} \right)

(𝑎𝑏)(ab)

方括号,中括号

1
\left[ \frac{a}{b} \right]

[𝑎𝑏][ab]

花括号,大括号

1
\left{ \frac{a}{b} \right}

{𝑎𝑏}{ab}

角括号

1
\left \langle \frac{a}{b} \right \rangle

⟨𝑎𝑏⟩⟨ab⟩

单竖线,绝对值

1
\left| \frac{a}{b} \right|

∣∣∣𝑎𝑏∣∣∣|ab|

双竖线,范

1
\left \| \frac{a}{b} \right \|

‖‖‖𝑎𝑏‖‖‖‖ab‖

取整函数

1
\left \lfloor \frac{a}{b} \right \rfloor

⌊𝑎𝑏⌋⌊ab⌋

取顶函数

1
\left \lceil \frac{c}{d} \right \rceil

⌈𝑐𝑑⌉⌈cd⌉

斜线与反斜线

1
\left / \frac{a}{b} \right \backslash

/𝑎𝑏/ab\

上下箭头

1
\left \uparrow \frac{a}{b} \right \downarrow

↑⏐⏐⏐⏐⏐𝑎𝑏⏐↓⏐⏐⏐⏐↑ab↓

1
\left \Uparrow \frac{a}{b} \right \Downarrow

⇑∥∥𝑎𝑏∥⇓∥⇑ab⇓

1
\left \updownarrow \frac{a}{b} \right \Updownarrow

↑↓⏐⏐𝑎𝑏⇑⇓∥↕ab⇕

混合括号

1
\left[ 0,1 \right)

[0,1)[0,1)

1
\left \langle \psi \right |

⟨𝜓∣∣⟨ψ|

如果括号只有一边,要用 \left.\right. 匹配另一边。

单左括号

1
\left \{\frac{a}{b} \right.

{𝑎𝑏{ab

单右括号

1
\left. \frac{a}{b} \right \}

𝑎𝑏}ab}

备注:

  • 可以使用 \big, \Big, \bigg, \Bigg 控制括号的大小,比如代码

    \Bigg ( \bigg [ \Big \{ \big \langle \left | \| \frac{a}{b} \| \right | \big \rangle \Big \} \bigg ] \Bigg )

    显示︰

([{⟨∣∣∣‖𝑎𝑏‖∣∣∣⟩}])([{⟨|‖ab‖|⟩}])

空格#

注意 TeX 能够自动处理大多数的空格,但是您有时候需要自己来控制。

功能|语法|显示|宽度

2 个 quad 空格#

1
\alpha\qquad\beta

𝛼𝛽αβ

𝑚𝑚mm

quad 空格#

1
\alpha\quad\beta

𝛼𝛽αβ

𝑚m

大空格#

1
\alpha\ \beta

𝛼 𝛽α β

𝑚3m3

中等空格#

1
\alpha\;\beta

𝛼𝛽αβ

2𝑚72m7

小空格#

1
\alpha\,\beta

𝛼𝛽αβ

𝑚6m6

没有空格#

1
\alpha\beta

𝛼𝛽αβ

00

紧贴#

1
\alpha\!\beta

𝛼𝛽αβ

−𝑚6−m6

颜色#

Cmd Markdown 公式指导手册里是这样写的:#

使用 \color{颜色}{文字} 来更改特定的文字颜色。
更改文字颜色 需要浏览器支持 ,如果浏览器不知道你所需的颜色,那么文字将被渲染为黑色。

对于较旧的浏览器(HTML4与CSS2),以下颜色是被支持的:

输入 显示 输入 显示
black 𝑡𝑒𝑥𝑡text grey 𝑡𝑒𝑥𝑡text
silver 𝑡𝑒𝑥𝑡text white 𝑡𝑒𝑥𝑡text
maroon 𝑡𝑒𝑥𝑡text red 𝑡𝑒𝑥𝑡text
yellow 𝑡𝑒𝑥𝑡text lime 𝑡𝑒𝑥𝑡text
olive 𝑡𝑒𝑥𝑡text green 𝑡𝑒𝑥𝑡text
teal 𝑡𝑒𝑥𝑡text auqa 𝑡𝑒𝑥𝑡text
blue 𝑡𝑒𝑥𝑡text navy 𝑡𝑒𝑥𝑡text
purple 𝑡𝑒𝑥𝑡text fuchsia 𝑡𝑒𝑥𝑡text

对于较新的浏览器(HTML5与CSS3),额外的124种颜色将被支持:

输入 \color { #rgb} {text} 来自定义更多的颜色,其中 #rgbr g b 可输入 0-9a-f 来表示红色、绿色和蓝色的纯度(饱和度)。

  • 例子:
1
2
3
4
5
6
7
8
9
10
11
Copy\begin{array}{|rrrrrrrr|}\hline
\verb+#000+ & \color{ #000}{text} & & &
\verb+#00F+ & \color{ #00F}{text} & & \\
& & \verb+#0F0+ & \color{ #0F0}{text} &
& & \verb+#0FF+ & \color{ #0FF}{text}\\
\verb+#F00+ & \color{ #F00}{text} & & &
\verb+#F0F+ & \color{ #F0F}{text} & & \\
& & \verb+#FF0+ & \color{ #FF0}{text} &
& & \verb+#FFF+ & \color{ #FFF}{text}\\
\hline
\end{array}
  • 显示:

    #𝟶𝟶𝟶#𝙵𝟶𝟶𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡#𝟶𝙵𝟶#𝙵𝙵𝟶𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡#𝟶𝟶𝙵#𝙵𝟶𝙵𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡#𝟶𝙵𝙵#𝙵𝙵𝙵𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡#000text#00Ftext#0F0text#0FFtext#F00text#F0Ftext#FF0text#FFFtext

  • 例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Copy\begin{array}{|rrrrrrrr|}
\hline
\verb+#000+ & \color{ #000}{text} & \verb+#005+ & \color{ #005}{text} & \verb+#00A+ & \color{ #00A}{text} & \verb+#00F+ & \color{ #00F}{text} \\
\verb+#500+ & \color{ #500}{text} & \verb+#505+ & \color{ #505}{text} & \verb+#50A+ & \color{ #50A}{text} & \verb+#50F+ & \color{ #50F}{text} \\
\verb+#A00+ & \color{ #A00}{text} & \verb+#A05+ & \color{ #A05}{text} & \verb+#A0A+ & \color{ #A0A}{text} & \verb+#A0F+ & \color{ #A0F}{text} \\
\verb+#F00+ & \color{ #F00}{text} & \verb+#F05+ & \color{ #F05}{text} & \verb+#F0A+ & \color{ #F0A}{text} & \verb+#F0F+ & \color{ #F0F}{text} \\
\hline
\verb+#080+ & \color{ #080}{text} & \verb+#085+ & \color{ #085}{text} & \verb+#08A+ & \color{ #08A}{text} & \verb+#08F+ & \color{ #08F}{text} \\
\verb+#580+ & \color{ #580}{text} & \verb+#585+ & \color{ #585}{text} & \verb+#58A+ & \color{ #58A}{text} & \verb+#58F+ & \color{ #58F}{text} \\
\verb+#A80+ & \color{ #A80}{text} & \verb+#A85+ & \color{ #A85}{text} & \verb+#A8A+ & \color{ #A8A}{text} & \verb+#A8F+ & \color{ #A8F}{text} \\
\verb+#F80+ & \color{ #F80}{text} & \verb+#F85+ & \color{ #F85}{text} & \verb+#F8A+ & \color{ #F8A}{text} & \verb+#F8F+ & \color{ #F8F}{text} \\
\hline
\verb+#0F0+ & \color{ #0F0}{text} & \verb+#0F5+ & \color{ #0F5}{text} & \verb+#0FA+ & \color{ #0FA}{text} & \verb+#0FF+ & \color{ #0FF}{text} \\
\verb+#5F0+ & \color{ #5F0}{text} & \verb+#5F5+ & \color{ #5F5}{text} & \verb+#5FA+ & \color{ #5FA}{text} & \verb+#5FF+ & \color{ #5FF}{text} \\
\verb+#AF0+ & \color{ #AF0}{text} & \verb+#AF5+ & \color{ #AF5}{text} & \verb+#AFA+ & \color{ #AFA}{text} & \verb+#AFF+ & \color{ #AFF}{text} \\
\verb+#FF0+ & \color{ #FF0}{text} & \verb+#FF5+ & \color{ #FF5}{text} & \verb+#FFA+ & \color{ #FFA}{text} & \verb+#FFF+ & \color{ #FFF}{text} \\
\hline
\end{array}
  • 显示:

#𝟶𝟶𝟶#𝟻𝟶𝟶#𝙰𝟶𝟶#𝙵𝟶𝟶#𝟶𝟾𝟶#𝟻𝟾𝟶#𝙰𝟾𝟶#𝙵𝟾𝟶#𝟶𝙵𝟶#𝟻𝙵𝟶#𝙰𝙵𝟶#𝙵𝙵𝟶𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡#𝟶𝟶𝟻#𝟻𝟶𝟻#𝙰𝟶𝟻#𝙵𝟶𝟻#𝟶𝟾𝟻#𝟻𝟾𝟻#𝙰𝟾𝟻#𝙵𝟾𝟻#𝟶𝙵𝟻#𝟻𝙵𝟻#𝙰𝙵𝟻#𝙵𝙵𝟻𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡#𝟶𝟶𝙰#𝟻𝟶𝙰#𝙰𝟶𝙰#𝙵𝟶𝙰#𝟶𝟾𝙰#𝟻𝟾𝙰#𝙰𝟾𝙰#𝙵𝟾𝙰#𝟶𝙵𝙰#𝟻𝙵𝙰#𝙰𝙵𝙰#𝙵𝙵𝙰𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡#𝟶𝟶𝙵#𝟻𝟶𝙵#𝙰𝟶𝙵#𝙵𝟶𝙵#𝟶𝟾𝙵#𝟻𝟾𝙵#𝙰𝟾𝙵#𝙵𝟾𝙵#𝟶𝙵𝙵#𝟻𝙵𝙵#𝙰𝙵𝙵#𝙵𝙵𝙵𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡𝑡𝑒𝑥𝑡#000text#005text#00Atext#00Ftext#500text#505text#50Atext#50Ftext#A00text#A05text#A0Atext#A0Ftext#F00text#F05text#F0Atext#F0Ftext#080text#085text#08Atext#08Ftext#580text#585text#58Atext#58Ftext#A80text#A85text#A8Atext#A8Ftext#F80text#F85text#F8Atext#F8Ftext#0F0text#0F5text#0FAtext#0FFtext#5F0text#5F5text#5FAtext#5FFtext#AF0text#AF5text#AFAtext#AFFtext#FF0text#FF5text#FFAtext#FFFtext

维基百科的数学公式教程里是这样写的:#

语法:{\color{颜色}表达式}

作者实测:在部分浏览器中,上面的语法可能是错误的(只将表达式的第一个字符着色),\color{颜色}{文字}的语法才是正确的。例如:

{\color{Red}abc}显示𝑎𝑏𝑐abc
\color{Red}{abc}显示𝑎𝑏𝑐abc

支持色调表:

ApricotApricot

AquamarineAquamarine

BittersweetBittersweet

BlackBlack

BlueBlue

BlueGreenBlueGreen

BlueVioletBlueViolet

BrickRedBrickRed

BrownBrown

BurntOrangeBurntOrange

CadetBlueCadetBlue

CarnationPinkCarnationPink

CeruleanCerulean

CornflowerBlueCornflowerBlue

CyanCyan

DandelionDandelion

DarkOrchidDarkOrchid

EmeraldEmerald

ForestGreenForestGreen

FuchsiaFuchsia

GoldenrodGoldenrod

GrayGray

GreenGreen

GreenYellowGreenYellow

JungleGreenJungleGreen

LavenderLavender

LimeGreenLimeGreen

MagentaMagenta

MahoganyMahogany

MaroonMaroon

MelonMelon

MidnightBlueMidnightBlue

MulberryMulberry

NavyBlueNavyBlue

OliveGreenOliveGreen

OrangeOrange

OrangeRedOrangeRed

OrchidOrchid

PeachPeach

PeriwinklePeriwinkle

PineGreenPineGreen

PlumPlum

ProcessBlueProcessBlue

PurplePurple

RawSiennaRawSienna

RedRed

RedOrangeRedOrange

RedVioletRedViolet

RhodamineRhodamine

RoyalBlueRoyalBlue

RoyalPurpleRoyalPurple

RubineRedRubineRed

SalmonSalmon

SeaGreenSeaGreen

SepiaSepia

SkyBlueSkyBlue

SpringGreenSpringGreen

TanTan

TealBlueTealBlue

ThistleThistle

TurquoiseTurquoise

VioletViolet

VioletRedVioletRed

WhiteWhite

WildStrawberryWildStrawberry

YellowYellow

YellowGreenYellowGreen

YellowOrangeYellowOrange

*注︰输入时第一个字母必需以大写输入,如\color{OliveGreen}

例子

  • {\color{Blue}x^3}+{\color{Brown}2x} - {\color{OliveGreen}1}

    𝑥2+2𝑥−1x2+2x−1

  • x_{\color{Maroon}1,2}=\frac{-b\pm\sqrt{{\color{Maroon}b^2-4ac}}}{2a}

    𝑥1,2=−𝑏±𝑏2−4𝑎𝑐‾‾‾‾‾‾‾‾√2𝑎x1,2=−b±b2−4ac2a

外部链接 #

R语言基础绘图

#R语言基础绘图 这是一期不怎么严谨的CSDN搬运专栏,主要介绍如何用R绘制基本统计图形 ##创建图形 ###图形的核心:plot()函数 plot()函数是R语言创建图形最基本的函数,plot()是一个泛型函数,真正被调用的函数依赖于对象所属的类

plot函数最基本的参数x,y,分别表示横纵坐标的取值向量
参数main指定标题,sub指定副标题,xlab和ylab分别指定x轴和y轴标签

###abline()函数和lines()函数
abline()函数可以用于向图中添加线条

例如:在当前图形中添加y=2+1*x描述的直线

1
abline(c(2,1))

abline()在编写时特意考虑了参数是回归结果的情形,因此如果参数是回归结果的对象,那么这个函数就会从lmout$coefficients中提取斜率和截距,然后画出这条直线

1
library("wooldridge")
1
2
3
```

```library("lmtest")
1
2
3
```

```lmout=lm(wage1$lwage~wage1$educ)
1
2


abline(lmout)

1
2
3
4
5
6
7
8
9
10
11
12

![](https://i.imgur.com/RekAKL4.png)

lines()函数同样用于向图中添加直线,尽管lines()有很多的参数,但是有两个最基本的参数,分别表示x轴的取值向量和y轴的取值向量.两个参数联合起来表示向当前图形添加的点对(x,y),并在之后依次用线条把它们连接起来

如果你只想画出线条,而不想将其中的连接点绘制出来,可以将参数type="1"添加到lines()或plot()中

还可以用plot()中的参数lty来指定线条类型,如指定实线还是虚线
###points()函数
points()函数可以向现有的图形中添加一系列的点对(x,y),每个点都用一种图形元素来表现

例如:向当前图中添加Exam1和Exam2成绩的散点图,其中每一个点都用"+"来标记

points(testscore$Exam1,testscor$Exam3,pch=”+”)

1
2
3
4
###legend()函数
legend()函数用于向拥有多条曲线的图中添加图例
###text()函数
利用text()函数可以在图形的任意位置加上一些文字

text(2.5,4,”abc”)

1
2
3
4
5
6

![](https://i.imgur.com/MYtGvpt.png)
这将在图形中点(2.5,4)的位置加上文字"abc",字符串的中心,将正好位于(2.5,4)
###locator()函数
调用locator()函数,然后在图形中需要的位置点击鼠标,这个函数就会返回你点中之处的坐标.利用locator()函数,可以将文字精确放在你想要的位置.
下面这个命令将会告诉R,你会在图中点击二次.

locator(2)

1
2

下面是一个简单的例子:点击之后,函数会返回一个列表,其元素x,y分别表示所点位置的横纵坐标

hist(c(12,5,13,25,16))

1
2
3

```
locator(2)

$x

[1] 12.06204 17.31315

$y

[1] 1.998410 1.032207
##改变图形
###改变字符大小:cex
cex选项用于放大或缩小图形中的字符,在许多绘图函数中,你都可以将它作为一个参数代入其中.
例如:输入以下命令可以实现对”abc”字符的放大输出

1
text(2.5,4,"abc",cex=1.5)

###改变坐标轴的范围:xlim和ylim选项
你可能希望x轴或y轴的范围比默认情况更大或更小,要做到这一点,你可以在plot()和points()函数中指定xlim和ylim参数来对坐标轴调整.

如果你要绘制多条曲线,又没有指定xlim或ylim,那么就应该首先绘制最高最宽的那条.否则R只会根据第一条曲线绘制图形,然后将最高的那些在顶部截断
###平滑散点:lowess()
可以用lowess()对散点拟合一条平滑的非参数回归线

例如:可以实现考试成绩之间的平滑

1
plot(testscores)
1
lines(lowess(testscores))


###绘制具有显式表达式的函数
如果你想绘制函数g(t)=t+1在0到5之间的图像,可以利用curve()函数

1
curve(x+1,0,5)

如果想向图中添加这条曲线,则可以使用add参数:add=”T”
###function(x)
同样,如果想绘制函数在某区间上的图像,也可以用function(x)说明对函数画图
例如想画pnorm函数在(-3,3)的图像

1
plot(function(x) pnorm(x,lower.tail=F), -3, 3)