Jekyll2022-12-03T11:49:01+00:00http://blog.woodcoding.com/atom.xml无人之岛程序员、学生、不修电脑woodcodingCTF入门练习-信息泄露2020-06-18T15:00:00+00:002020-06-18T15:00:00+00:00http://blog.woodcoding.com/ctf/2020/06/18/ctf-info-leak-basic<h3 id="简介">简介</h3>
<p> 今天技能树加点的是信息泄露方面的内容,主要有目录遍历、PHPINFO、备份文件下载、Git泄露、SVN泄露、HG泄露等几个方面的内容。<!-- more --></p>
<h3 id="目录遍历">目录遍历</h3>
<ul>
<li>
<p>题目简介</p>
<p>题目给出一个网址,打开之后可以浏览服务器提供的目录</p>
</li>
<li>
<p>解题思路</p>
<p>这题简单,就是去目录下找到flag文件即可。我看还有通过nginx配置漏洞去找系统级目录的,那个应该难一些。</p>
</li>
<li>
<p>相关知识</p>
<p>主要是nginx、apache配置目录浏览问题。有时候公司内部可能为了方便提供文件下载使用这种方式,但是配置不当可能会被利用。</p>
</li>
<li>
<p>题解代码</p>
<p>无</p>
</li>
</ul>
<h3 id="phpinfo">PHPINFO</h3>
<ul>
<li>
<p>题目简介</p>
<p>题目给出一个网址,打开之后可以查看服务器php版本的信息</p>
</li>
<li>
<p>解题思路</p>
<p>这题也简单,直接ctrl+f找页面中的flag就成。</p>
</li>
<li>
<p>相关知识</p>
<p>php探针</p>
</li>
<li>
<p>题解代码</p>
<p>无</p>
</li>
</ul>
<h3 id="备份文件下载">备份文件下载</h3>
<h4 id="bak文件">bak文件</h4>
<ul>
<li>
<p>题目简介</p>
<p>当开发人员在线上环境中对源代码进行了备份操作,并且将备份文件放在了 web 目录下,就会引起网站源码泄露。常见的就是bak结尾的文件。</p>
</li>
<li>
<p>解题思路</p>
<p>枚举一些常见的备份文件名即可。</p>
</li>
<li>
<p>相关知识</p>
<p>类似php、asp这种网站可以通过虚拟主机控制面板直接线上备份源码,备份在网站目录下的可能会被访问到,因此备份尽量放在其他目录。</p>
</li>
<li>
<p>题解代码</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="n">suffix_list</span> <span class="o">=</span> <span class="p">[</span><span class="s">'tar'</span><span class="p">,</span> <span class="s">'tar.gz'</span><span class="p">,</span> <span class="s">'zip'</span><span class="p">,</span> <span class="s">'rar'</span><span class="p">]</span>
<span class="n">filename_list</span> <span class="o">=</span> <span class="p">[</span><span class="s">'web'</span><span class="p">,</span> <span class="s">'website'</span><span class="p">,</span> <span class="s">'backup'</span><span class="p">,</span> <span class="s">'back'</span><span class="p">,</span> <span class="s">'www'</span><span class="p">,</span> <span class="s">'wwwroot'</span><span class="p">,</span> <span class="s">'temp'</span><span class="p">]</span>
<span class="n">url</span> <span class="o">=</span> <span class="s">'http://challenge-b2bb6e0f371b7f16.sandbox.ctfhub.com:10080/'</span>
<span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">filename_list</span><span class="p">:</span>
<span class="k">for</span> <span class="n">suffix</span> <span class="ow">in</span> <span class="n">suffix_list</span><span class="p">:</span>
<span class="n">full_filename</span> <span class="o">=</span> <span class="s">'{name}.{suffix}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">filename</span><span class="p">,</span> <span class="n">suffix</span><span class="o">=</span><span class="n">suffix</span><span class="p">)</span>
<span class="n">file_url</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">full_filename</span><span class="p">)</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">file_url</span><span class="p">)</span>
<span class="k">if</span> <span class="n">resp</span><span class="p">.</span><span class="n">status_code</span> <span class="o">!=</span> <span class="mi">404</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="n">file_url</span><span class="p">)</span>
</code></pre></div> </div>
</li>
</ul>
<h4 id="vim缓存">vim缓存</h4>
<ul>
<li>
<p>题目简介</p>
<p>当开发人员在线上环境中使用 vim 编辑器,在使用过程中会留下 vim 编辑器缓存,当vim异常退出时,缓存会一直留在服务器上,引起网站源码泄露。</p>
</li>
<li>
<p>解题思路</p>
<p>vim在正常退出时会在当前目录保留一份.filename.swp的文件(如果在恢复文件中再次非正常退出又会生成.swo文件…)。获取到swp文件后直接用<code class="language-plaintext highlighter-rouge">vim -r .filename.swp</code>就能看到文件详情了。</p>
</li>
<li>
<p>相关知识</p>
<p>vim缓存文件相关知识,最好不要在生产服务器使用vim进行修改。</p>
</li>
<li>
<p>题解代码</p>
<p>无</p>
</li>
</ul>
<h4 id="ds_store">.DS_store</h4>
<ul>
<li>
<p>题目简介</p>
<p>.DS_Store 是 Mac OS 保存文件夹的自定义属性的隐藏文件。通过.DS_Store可以知道这个目录里面所有文件的清单。</p>
</li>
<li>
<p>解题思路</p>
<p>获得.DS_Store文件后可以使用工具可以从文件中读取到文件夹目录下的文件,在网站上访问flag文件即可获得。</p>
</li>
<li>
<p>相关知识</p>
<p>.DS_Store是Mac中保存文件夹信息的文件。拷贝文件时千万不要拷贝这个文件。</p>
</li>
<li>
<p>题解代码</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># https://github.com/gehaxelt/Python-dsstore</span>
python main.py samples/.DS_Store.ctf
</code></pre></div> </div>
</li>
</ul>
<h3 id="git泄露">Git泄露</h3>
<ul>
<li>
<p>题目简介</p>
<p>当前大量开发人员使用git进行版本控制,对站点自动部署。如果配置不当,可能会将.git文件夹直接部署到线上环境。这就引起了git泄露漏洞。</p>
</li>
<li>
<p>解题思路</p>
<p>可以通过工具下载网站上的.git目录,本地再进行信息探索即可。不过要注意不要过于依赖某个工具,因为我之前通过<a href="https://github.com/WangYihang/GitHacker">GitHacker</a>查到了通过log泄露的flag,但是后面的stash和index却提示错误,似乎并没有正确重建,所以拿不到数据。后来换了另外一个<a href="https://github.com/gakki429/Git_Extract">Git_Extract</a>直接提取文件恢复拿到了stash和index泄露的flag。</p>
</li>
<li>
<p>相关知识</p>
<p>.git文件夹是Git版本控制所用到的。所以即使我们删除了文件如果不删除.git文件夹,我们依然可以恢复到之前某个版本,获得相应的文件。因此,切记不可把.git文件夹放到网站服务器上。</p>
</li>
<li>
<p>题解代码</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># https://github.com/WangYihang/GitHacker</span>
<span class="c"># https://github.com/gakki429/Git_Extract</span>
<span class="c"># 使用对应工具进行操作。</span>
</code></pre></div> </div>
</li>
</ul>
<h3 id="svn泄露">SVN泄露</h3>
<ul>
<li>
<p>题目简介</p>
<p>当开发人员使用 SVN 进行版本控制,对站点自动部署。如果配置不当,可能会将.svn文件夹直接部署到线上环境。这就引起了 SVN 泄露漏洞。</p>
</li>
<li>
<p>解题思路</p>
<p>使用<code class="language-plaintext highlighter-rouge">dvcs-ripper</code>工具箱下面的rip-svn.pl(注意在自己系统上使用的话可能要安装svn软件等,可能会存在一些版本依赖问题,很难装上,可以使用封装的docker进行操作会比较方便)。svn中wc.db记录一些版本相关信息,在wc.db中可以看见flag文件,但是访问却显示不存在,继而转<code class="language-plaintext highlighter-rouge">.svn/pristine/</code>目录直接搜索对象文件即可找到flag。</p>
</li>
<li>
<p>相关知识</p>
<p>和Git差不多,SVN版本控制使用了.svn文件夹。dvcs-ripper是一款perl的版本控制软件信息泄露利用工具,支持SVN, GIT, Mercurial/hg等。</p>
</li>
<li>
<p>题解代码</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># https://github.com/kost/dvcs-ripper</span>
<span class="c"># 使用对应工具进行操作。</span>
</code></pre></div> </div>
</li>
</ul>
<h3 id="hg泄露">HG泄露</h3>
<ul>
<li>
<p>题目简介</p>
<p>当开发人员使用 Mercurial 进行版本控制,对站点自动部署。如果配置不当,可能会将.hg 文件夹直接部署到线上环境。这就引起了 hg 泄露漏洞。</p>
</li>
<li>
<p>解题思路</p>
<p>使用<code class="language-plaintext highlighter-rouge">dvcs-ripper</code>工具箱下面的rip-hg.pl。clone的仓库可能并不完整,所以可以去<code class="language-plaintext highlighter-rouge">.hg/store/fncache</code>目录找文件列表,找到可以直接访问获取。如果文件被删除,则从历史记录<code class="language-plaintext highlighter-rouge">.hg/store/data/</code>中寻找。</p>
</li>
<li>
<p>相关知识</p>
<p>Mercurial 是一种轻量级分布式版本控制系统,采用 Python 语言实现,易于学习和使用,扩展性强。需要了解其目录结构。</p>
</li>
<li>
<p>题解代码</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># https://github.com/kost/dvcs-ripper</span>
<span class="c"># 使用对应工具进行操作。</span>
</code></pre></div> </div>
</li>
</ul>woodcoding简介 今天技能树加点的是信息泄露方面的内容,主要有目录遍历、PHPINFO、备份文件下载、Git泄露、SVN泄露、HG泄露等几个方面的内容。CTF入坑介绍2020-06-16T15:00:00+00:002020-06-16T15:00:00+00:00http://blog.woodcoding.com/ctf/2020/06/16/ctf-basic<h3 id="简介">简介</h3>
<p> CTF(Capture The Flag,夺旗赛)CTF 的前身是传统黑客之间的网络技术比拼游戏,起源于 1996 年第四届 DEFCON,以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。 CTF是一种流行的信息安全竞赛形式,其英文名可直译为“夺得Flag”,也可意译为“夺旗赛”。<!-- more -->其大致流程是,参赛团队之间通过进行攻防对抗、程序分析等形式,率先从主办方给出的比赛环境中得到一串具有一定格式的字符串或其他内容,并将其提交给主办方,从而夺得分数。为了方便称呼,我们把这样的内容称之为“Flag”。 flag所表示的为目标服务器上存储的一些敏感机密的信息, 这些信息正常情况下是不能对外暴露的。选手利用目标的一些漏洞,获取到flag,其表示的即为在真实的黑客攻击中窃取到的机密信息。 一般情况下flag拥有固定格式为flag{xxxxx},有些比赛会把flag关键词替换,例如我们CTFHub平台的flag为ctfhub{xxxxx},利用固定格式来反推flag也是一种常见的解题思路 通常来说CTF是以团队为单位进行参赛。每个团队3-5人(具体根据主办方要求决定),在整个比赛过程中既要每个选手拥有某个方向的漏洞挖掘能力,也要同队选手之间的相互配合。</p>
<h3 id="竞赛模式">竞赛模式</h3>
<p>CTF竞赛模式大概分7种类型。</p>
<ul>
<li>理论知识</li>
<li>Jeopardy-解题</li>
<li>AwD-攻防模式</li>
<li>RHG-自动化[AI自动化]</li>
<li>RW-真实世界</li>
<li>KoH-抢占山头</li>
<li>Mix[混合]</li>
</ul>
<h4 id="理论知识">理论知识</h4>
<p>理论题多见于国内比赛,通常为选择题。包含单选及多选,选手需要根据自己所学的相关理论知识进行作答。最终得出分数。理论部分通常多见于初赛或是初赛之前的海选</p>
<h4 id="jeopardy-解题">Jeopardy-解题</h4>
<p>参赛队伍可以通过互联网或者现场网络参与,参数队伍通过与在线环境交互或文件离线分析,解决网络安全技术挑战获取相应分值,类似于 ACM 编程竞赛、信息学奥林匹克赛,根据总分和时间来进行排名。</p>
<p>不同的是这个解题模式一般会设置 一血(First Blood) 、 二血(Second Blood) 、 三血(Third Blood) ,也即最先完成的前三支队伍会获得额外分值,所以这不仅是对首先解出题目的队伍的分值鼓励,也是一种团队能力的间接体现。</p>
<p>当然还有一种流行的计分规则是设置每道题目的初始分数后,根据该题的成功解答队伍数,来逐渐降低该题的分值,也就是说如果解答这道题的人数越多,那么这道题的分值就越低。最后会下降到一个保底分值后便不再下降。一般称之为<strong>动态积分</strong></p>
<p>题目类型主要包含 Web 网络攻防 、 RE 逆向工程 、 Pwn 二进制漏洞利用 、 Crypto 密码攻击以及 Misc 安全杂项 这五个类别,个别比赛会根据题目类型进行扩展。</p>
<h4 id="awd-攻防模式">AwD-攻防模式</h4>
<p>Attack with Defense(AwD)全称攻防模式,在攻防模式CTF赛制中,参赛队伍连接到同一个网络空间。主办方会预先为每个参赛队分配要防守的主机,该主机称之为GameBox,每个队伍之间的GameBox配置及漏洞是完全一致的,选手需要防护自己的GameBox不被攻击的同时挖掘漏洞并攻击对手服务来得分。在AwD中主办方会运行一个名为Checker的程序定时检测选手的GameBox的运行状态。若检测到状态不对则判定该GameBox宕机,按照规则扣除一定分数。攻防模式CTF赛制可以实时通过得分反映出比赛情况,最终也以得分直接分出胜负,是一种竞争激烈,具有很强观赏性和高度透明性的网络安全赛制。在这种赛制中,不仅仅是比参赛队员的智力和技术,也比体力(因为比赛一般都会持续24至48小时左右),同时也比团队之间的分工配合与合作。 AwD通常仅包含Web及Pwn两种类型的题目。每个队伍可能会分到多个GameBox,随着比赛的进行,最早的GameBox可能会下线,同时会上线新的GameBox。</p>
<h4 id="rhg-自动化ai自动化">RHG-自动化[AI自动化]</h4>
<p>Robo Hacking Game(RHG)该利用人工智能或是AI或是自动化攻击程序来全自动的挖掘并利用漏洞,考验选手对于漏洞理解以及工程化能力。比赛开始前(一般为1-4周左右)主办方会给出测试环境以及相关接口文档。选手需要编写自动化程序来请求接口获取题目相关信息,该类程序通常称之为bot,在程序中全自动访问并挖掘目标漏洞,完成利用漏洞攻击并获取flag的过程。获取到的flag也由程序自动化提交。RHG因为是由bot全自动进行工作,所以比赛开始即可视为结束。剩下的一切全看参赛选手编写的自动化bot的工作情况。 比赛过程中不允许选手对bot进行任何的操作(包括debug/patch等等)。选手仅能看到自己的bot完成了哪些题。目前的得分情况等等。</p>
<h4 id="rw-真实世界">RW-真实世界</h4>
<p>Real World(RW) 首次于2018年长亭科技主办的RealWorldCTF中出现,该赛制着重考察选手在面对真实的环境下的漏洞挖掘与利用能力。通常RW模式出题也会围绕着能够应用于真实渗透攻击当中的漏洞,一般来说RW常见题型为VM/Docker逃逸、针对浏览器的攻击、针对IoT/Car等设备的攻击,Web类攻击等等 在RW赛制中会有一个Show Time,当选手认为自己已经可以完成题目时,选手可以在比赛平台上提交展示申请,由工作人员根据申请先后顺序进行展示排期。选手展示之前需要上台并连接相关网络,同时现场大屏会切换至目标的正常页面。选手确认连接并测试OK之后开始计时。一般情况下上台攻击的时间为5分钟,选手一旦完成攻击现场大屏幕会实时看到攻击的效果,此时裁判会根据效果是否符合题目要求来判定该题是否完成。如5在攻击时间内依然未能看到展示效果则认为本次攻击失败。现如今为了防止选手恶意排期。通常会有一个队伍总展示次数(例如在2019年数字经济云安全公测大赛中每个队伍只允许上台展示30次),选手也需要尽可能保证上台之后攻击的成功率 举个例子。题目要求需要攻击位于比赛网络中的某个网站并将首页替换为包含队伍名称的页面。题目给出该网站的一些信息(源代码/数据库等等),选手经过本地挖掘漏洞之后,提交展示申请,排期到了之后进行上台展示。注意,因为RW模式是以展示效果来作为题目是否完成的准则,所以在RW模式中并不存在Flag。</p>
<h4 id="koh-抢占山头">KoH-抢占山头</h4>
<p>King of Hill(KoH)是近些年新衍生的一种赛制。该赛制有点类似于AwD,但是又和AwD有些不一样。选手面对的是一个黑盒的目标,需要先挖掘漏洞并利用漏洞控制目标。将自己的队伍标识(队伍名称或是Token之类)写入到指定文件。随后在该主机上进行加固等操作防止其他队伍攻击,主办方会定期去检查标识文件,根据文件中的队伍标识来判定本回合分数给予哪个队伍。可以看出KoH也是一种对抗极为激烈的赛制,同时考察选手的渗透能力及防御加固能力。</p>
<h4 id="mix混合">Mix[混合]</h4>
<p>混合模式结合了以上多种模式,现如今单一的赛制已经无法满足比赛及选手的参赛需求,所以大部分比赛会同时以多个模式进行比赛。例如参赛队伍通过解题(Jeopardy)可以获取一些初始分数,然后通过攻防对抗(AwD)进行得分增减的零和游戏,最终以得分高低分出胜负。</p>
<h3 id="比赛形式">比赛形式</h3>
<p>CTF比赛一般分为线上赛和线下赛。通常来说,线上赛多为初赛, 线下赛多为决赛, 但是也不排除直接进行</p>
<h4 id="线上">线上</h4>
<p>选手通过主办方搭建的比赛平台在线注册,在线做题并提交flag,线上比赛多为解题模式,攻防模式较为少见。通常来说对于长时间未解出的题目,主办方会酌情给出提示(Hint)来帮助选手做题。</p>
<h4 id="线下">线下</h4>
<p>选手前往比赛所在地,现场接入比赛网络进行比赛,线下多为AWD模式,近年来随着比赛赛制的不断革新,线下赛也会出现多种模式混合进行,例如结合解题+AWD,解题+RW 等等</p>
<h3 id="题目类型">题目类型</h3>
<p>在CTF中主要包含5个大类的题目:</p>
<ul>
<li>Web</li>
<li>Pwn</li>
<li>Reverse</li>
<li>Crypto</li>
<li>Misc</li>
</ul>
<p>有些比赛会根据自己的侧重点单独添加某个分类,例如移动设备(Mobile), 电子取证(Forensics)等,近年来也会出来混合类型的题目,例如在Web中存在一个二进制程序,需要选手先利用Web的漏洞获取到二进制程序,之后通过逆向或是Pwn等方式获得最终flag。</p>
<h4 id="web">Web</h4>
<p>Web类题目大部分情况下和网、Web、HTTP等相关技能有关。主要考察选手对于Web攻防的一些知识技巧。诸如SQL注入、XSS、代码执行、代码审计等等都是很常见的考点。
一般情况下Web题目只会给出一个能够访问的URL。部分题目会给出附件</p>
<h4 id="pwn">Pwn</h4>
<p>Pwn类题目重点考察选手对于二进制漏洞的挖掘和利用能力,其考点也通常在堆栈溢出、格式化漏洞、UAF、Double Free等常见二进制漏洞上。选手需要根据题目中给出的二进制可执行文件进行逆向分析,找出其中的漏洞并进行利用,编写对应的漏洞攻击脚本(Exploit),进而对主办方给出的远程服务器进行攻击并获取flag
通常来说Pwn类题目给出的远程服务器信息为nc IP_ADDRESS PORT,例如nc 1.2.3.4 4567这种形式,表示在1.2.3.4这个IP的4567端口上运行了该题目</p>
<h4 id="reverse">Reverse</h4>
<p>Re类题目考察选手逆向工程能力。题目会给出一个可执行二进制文件,有些时候也可能是Android的APK安装包。选手需要逆向给出的程序,分析其程序工作原理。最终根据程序行为等获得flag</p>
<h4 id="crypto">Crypto</h4>
<p>Crypto类题目考察选手对密码学相关知识的了解程度,诸如RSA、AES、DES等都是密码学题目的常客。
有些时候也会给出一个加密脚本和密文,根据加密流程逆推出明文。</p>
<h4 id="misc">Misc</h4>
<p>Misc意为杂项,即不包含在以上分类的题目都会放到这个分类。题目会给出一个附件。选手下载该附件进行分析,最终得出flag
常见的题型有图片隐写、视频隐写、文档隐写、流量分析、协议分析、游戏、IoT相关等等。五花八门,种类繁多。</p>
<p>参考资料:</p>
<ul>
<li>https://writeup.ctfhub.com/Skill/CTF%E5%9F%BA%E7%A1%80/22435.html</li>
<li>https://writeup.ctfhub.com/Skill/CTF%E5%9F%BA%E7%A1%80/44044.html</li>
<li>https://writeup.ctfhub.com/Skill/CTF%E5%9F%BA%E7%A1%80/52169.html</li>
<li>https://writeup.ctfhub.com/Skill/CTF%E5%9F%BA%E7%A1%80/63374.html</li>
<li>https://ctf-wiki.github.io/ctf-wiki/introduction/resources-zh/</li>
</ul>woodcoding简介 CTF(Capture The Flag,夺旗赛)CTF 的前身是传统黑客之间的网络技术比拼游戏,起源于 1996 年第四届 DEFCON,以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。 CTF是一种流行的信息安全竞赛形式,其英文名可直译为“夺得Flag”,也可意译为“夺旗赛”。CTF入门练习-HTTP基础2020-06-16T15:00:00+00:002020-06-16T15:00:00+00:00http://blog.woodcoding.com/ctf/2020/06/16/ctf-http-basic<h3 id="简介">简介</h3>
<p> 在CTF上面有个技能树,先把里面的基础练习做一遍。感觉CTF对知识面的广度要求非常高,这是个很好的学习机会。今天是HTTP协议相关的题目。<!-- more --></p>
<h3 id="题目列表">题目列表</h3>
<h4 id="1请求方式">1.请求方式</h4>
<ul>
<li>题目简介</li>
</ul>
<p>HTTP 请求方法, HTTP/1.1协议中共定义了八种方法(也叫动作)来以不同方式操作指定的资源。</p>
<ul>
<li>解题思路</li>
</ul>
<p>打开题目给出的网址过后,显示如下内容的网页:</p>
<blockquote>
<p>HTTP Method is GET
Use CTF**B Method, I will give you flag.
Hint: If you got 「HTTP Method Not Allowed」 Error, you should request index.php.</p>
</blockquote>
<p>显然,题目是想我们通过不同的HTTP方法请求来获取flag。但是刚开始理解题目可能有点难度,毕竟<code class="language-plaintext highlighter-rouge">CTF**B</code>他是想表达个啥嘛。我还以为是举个例子,然后用HTTP1.1的9种(题目写的8种,漏了patch)方法去请求,结果都没有看到flag。最后想想,有没有可能是除了这八大方法之外,我也可以使用自己定义的方法呢?于是搜索pyhton库里面使用自定义方法的教程,然后发起一个<code class="language-plaintext highlighter-rouge">CTFHUB</code>方式的请求,flag就出来了。</p>
<ul>
<li>
<p>相关知识</p>
<p>HTTP1.1请求定义的九种方法:</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>序号</th>
<th>方法</th>
<th>介绍</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>GET</td>
<td>请求指定的页面信息,并返回实体主体。</td>
</tr>
<tr>
<td>2</td>
<td>HEAD</td>
<td>类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头</td>
</tr>
<tr>
<td>3</td>
<td>POST</td>
<td>向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。</td>
</tr>
<tr>
<td>4</td>
<td>PUT</td>
<td>从客户端向服务器传送的数据取代指定的文档的内容。</td>
</tr>
<tr>
<td>5</td>
<td>DELETE</td>
<td>请求服务器删除指定的页面。</td>
</tr>
<tr>
<td>6</td>
<td>CONNECT</td>
<td>HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。</td>
</tr>
<tr>
<td>7</td>
<td>OPTIONS</td>
<td>允许客户端查看服务器的性能。</td>
</tr>
<tr>
<td>8</td>
<td>TRACE</td>
<td>回显服务器收到的请求,主要用于测试或诊断。</td>
</tr>
<tr>
<td>9</td>
<td>PATCH</td>
<td>是对 PUT 方法的补充,用来对已知资源进行局部更新 。</td>
</tr>
</tbody>
</table>
<p><strong>注意:</strong> 特定的HTTP服务器支持自定义的HTTP请求方法。</p>
<ul>
<li>题解代码</li>
</ul>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">urllib</span> <span class="kn">import</span> <span class="n">request</span>
<span class="n">req</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">Request</span><span class="p">(</span><span class="n">method</span><span class="o">=</span><span class="s">'CTFHUB'</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="s">'http://challenge-7c8a21f5ac0bee47.sandbox.ctfhub.com:10080/index.php'</span><span class="p">)</span>
<span class="n">res</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">res</span><span class="p">.</span><span class="n">read</span><span class="p">())</span>
</code></pre></div></div>
<h4 id="2-302跳转">2. 302跳转</h4>
<ul>
<li>
<p>题目简介</p>
<p>HTTP临时重定向</p>
</li>
<li>
<p>解题思路</p>
<p>打开题目给出的网址后,显示如下内容:</p>
<p><img src="/resources/images/2020-06-16/2020-06-16-17-48-06.png" alt="" /></p>
</li>
</ul>
<p>由于是重定向,所以打开浏览器控制台我们可以发现,前往index.php的请求被临时重定向到了index.html,所以就想到,flag可能在index.php中,但是浏览器会自动跳转所以我们根本看不到内容,只能写代码阻止跳转,然后直接查看内容。</p>
<ul>
<li>
<p>相关知识</p>
<p>重定向的知识我之前有写一篇博客,这里就不多BB了。</p>
</li>
<li>
<p>题解代码</p>
</li>
</ul>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span>
<span class="n">url</span> <span class="o">=</span> <span class="s">'http://challenge-df0e9edbdeb14ca4.sandbox.ctfhub.com:10080/index.php'</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">allow_redirects</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">resp</span><span class="p">.</span><span class="n">text</span><span class="p">)</span>
</code></pre></div></div>
<h4 id="3-cookie">3. Cookie</h4>
<ul>
<li>
<p>题目简介</p>
<p>Cookie欺骗、认证、伪造</p>
</li>
<li>
<p>题解思路</p>
<p>打开题目给的网址后,显示如下内容。</p>
<blockquote>
<p>hello guest. only admin can get flag.</p>
</blockquote>
<p>既然是cookie,那肯定是cookie里面设置了权限。打开浏览器控制台如下图,果然看到admin有个值为0,改成1,ok,看到flag。</p>
<p><img src="/resources/images/2020-06-16/2020-06-16-15-21-17.png" alt="" /></p>
</li>
<li>
<p>相关知识</p>
<p>cookie就是在浏览器本地存储一些用户相关的信息,用于动态网页的显示,早期互联网发展的时候还有在cookie存用户敏感信息的,但是这非常不安全,容易被窃取用于其他目的,因此在web开发中我们一定不能往cookie中存敏感信息,不然就容易像这个题目一样明明管理员才能看的就被我们看到了。</p>
</li>
<li>
<p>题解代码</p>
<p>这题我直接改浏览器的,所以没写代码,但是要写也可以,传个cookie参数,把admin设为1就行。</p>
</li>
</ul>
<h4 id="4-基础认证">4. 基础认证</h4>
<ul>
<li>
<p>题目简介</p>
<p>在HTTP中,基本认证(英语:Basic access authentication)是允许http用户代理(如:网页浏览器)在请求时,提供 用户名 和 密码 的一种方式。详情请查看 https://zh.wikipedia.org/wiki/HTTP基本认证</p>
</li>
<li>
<p>解题思路</p>
<p>这种认证之前在路由器管理界面、nginx代理认证这些上面都有见过,对于单用户管理系统来说可以简化用户系统模块,是很方便的。打开题目给的网址,显示如下:</p>
<p><img src="/resources/images/2020-06-16/2020-06-16-17-47-35.png" alt="" /></p>
<p>由于题目给出了100个密码,但是没有用户名,所以根据wiki给的知识,我们可以先往目标链接发一个请求,发现Authorization头里面有一句话:<code class="language-plaintext highlighter-rouge">Do you know admin?</code>(大概是这样的,忘记记录了)。所以admin肯定是用户名,但是问题又来了,这100个密码难道手动输入?不可能!直接上代码即可。</p>
</li>
<li>
<p>题解代码</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">def</span> <span class="nf">http_basic_auth</span><span class="p">():</span>
<span class="n">auth_url</span> <span class="o">=</span> <span class="s">'http://challenge-49903ae2a973e552.sandbox.ctfhub.com:10080/flag.html'</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'10_million_password_list_top_100.txt'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">'utf-8'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="k">for</span> <span class="n">pwd</span> <span class="ow">in</span> <span class="n">f</span><span class="p">.</span><span class="n">readlines</span><span class="p">():</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">auth_url</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="p">(</span><span class="s">'admin'</span><span class="p">,</span> <span class="n">pwd</span><span class="p">.</span><span class="n">strip</span><span class="p">()))</span>
<span class="k">if</span> <span class="n">resp</span><span class="p">.</span><span class="n">status_code</span> <span class="o">!=</span> <span class="mi">401</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="n">resp</span><span class="p">.</span><span class="n">text</span><span class="p">)</span>
</code></pre></div> </div>
</li>
</ul>
<h4 id="5-响应包源代码">5. 响应包源代码</h4>
<ul>
<li>
<p>题目简介</p>
<p>HTTP响应包源代码查看</p>
</li>
<li>
<p>解题思路</p>
<p>这题太简单了,直接右键查看源代码,flag就在注释里面。</p>
<pre><code class="language-HTML"><!-- ctfhub{efff37a833235e5ff56b5e648276eb7d5b2d****} -->
</code></pre>
</li>
<li>
<p>相关知识</p>
<p>HTML页面的源码是可以直接在浏览器右键查看的,但是这源码并不是我们后台系统的真正源码,是动态生成后的显示效果源码。By the way,现在国内B/S系统这么流行,不会还有人不知道怎么看页面源码吧?</p>
</li>
<li>
<p>题解代码
这题也是不需要代码的,直接右键多方便,要写代码的话,直接打印目标链接的内容就行了(这也太麻烦了)。</p>
</li>
</ul>
<h3 id="参考资料">参考资料</h3>
<ul>
<li>https://www.runoob.com/http/http-methods.html</li>
<li>https://blog.woodcoding.com/flask/2020/02/21/http-300-code-with-flask-form-anchor/</li>
</ul>woodcoding简介 在CTF上面有个技能树,先把里面的基础练习做一遍。感觉CTF对知识面的广度要求非常高,这是个很好的学习机会。今天是HTTP协议相关的题目。个人(小团队)开发工具介绍2020-06-06T15:00:00+00:002020-06-06T15:00:00+00:00http://blog.woodcoding.com/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/2020/06/06/dev-tools<p> 平时自己有写一些小项目,但是写小项目的过程中如果能有一些好用的工具来提升效率的话就最好了。比如自动化测试、自动发布、代码自动检查等功能。今天就来谈谈我自己在用或者接触过的一些工具。</p>
<!-- more -->
<h3 id="git服务器">Git服务器</h3>
<p>虽然全球最大同性交友网站github现在对私人的仓库也免费开放了,但是有些代码我们还是自己保存比较妥当,特别是近几年鹰酱老是搞些小动作,什么科学无国界、开源无国界都变扯淡了。所以搭建一个私人的git服务器是非常有必要的,下面就来谈谈常见的几种可以取代github的git服务器吧。</p>
<h4 id="gitlab">GitLab</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-15-50-40.png" alt="" /></p>
<ul>
<li>官网:<a href="https://about.gitlab.com/">https://about.gitlab.com/</a></li>
</ul>
<p>GitLab 是由 GitLab Inc.开发,一款基于 Git 的完全集成的软件开发平台。另外,GitLab 且具有wiki以及在线编辑、issue跟踪功能、CI/CD 等功能。可以说在这个领域是很强大了,类似全家桶的存在,连CICD、看板这些东西一套都给你上了。</p>
<ul>
<li>优点:集成代码开发全家桶,包括持续集成、持续交付、任务看板、工单系统、pages等几乎所有功能,对中大型企业来说是很方便的。</li>
<li>缺点:功能多的收费,只能使用社区版免费。吃配置,2G起跑、4G偶尔还500错误,要是不缺钱能堆配置就可以考虑。</li>
</ul>
<h4 id="gogs">gogs</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-15-59-11.png" alt="" /></p>
<ul>
<li>官网:<a href="https://gogs.io/">https://gogs.io/</a></li>
<li>开源地址:<a href="https://github.com/gogs/gogs">https://github.com/gogs/gogs</a></li>
</ul>
<p>Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 所有平台,包括 Linux、Mac OS X、Windows 以及 ARM 平台。</p>
<ul>
<li>优点:免费,轻量级,资源占用少,支持多平台,可以二进制部署也可以docker部署等。</li>
<li>缺点:功能太少,更新慢,听说是某个不太愿意接受社区贡献的大神写的,因此衍生出了后面的gitea。</li>
</ul>
<h4 id="gitea">gitea</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-16-06-25.png" alt="" /></p>
<ul>
<li>官网:<a href="https://gitea.io/">https://gitea.io/</a></li>
<li>开源地址:<a href="https://github.com/go-gitea/gitea">https://github.com/go-gitea/gitea</a></li>
</ul>
<p>Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。他和GitHub, Bitbucket or Gitlab等比较类似。他是从 Gogs 发展而来,不过已经Fork并且命名为Gitea。Gitea是流行的自托管Git服务Gogs的社区分支,是由不断增长的前Gogs用户和贡献者组成的小组,他们发现Gogs的单一维护者管理模型令人沮丧,因此决定努力建立一个更加开放和更快的开发模型。</p>
<p>同Gogs一样,Gitea的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。采用Go作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了x86,amd64,还包括 ARM 和 PowerPC。</p>
<ul>
<li>优点:免费,轻量级,资源占用少,支持多平台,可以二进制部署也可以docker部署等。功能比Gogs多,相当于Gogs plus版本。推荐使用此版本,本人目前也在使用Gitea。</li>
<li>缺点:功能没有gitlab那么多,CI/CD的支持还是要自己搭建然后使用钩子连接,不过还好有很多CI/CD工具可用。</li>
</ul>
<h4 id="gitbucket">gitbucket</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-16-15-59.png" alt="" /></p>
<ul>
<li>官网:<a href="https://gitbucket.github.io/">https://gitbucket.github.io/</a></li>
<li>开源地址:<a href="https://github.com/gitbucket/gitbucket">https://github.com/gitbucket/gitbucket</a></li>
</ul>
<p>GitBucket 是JVM上的开源Git平台由Scala提供支持的Git平台,具有易于安装,高度可扩展性和GitHub API兼容性的特点。只需下载并运行<code class="language-plaintext highlighter-rouge">java -jar gitbucket.war</code>。GitBucket是开源的,可在遵循Apache License Version 2.0协议下使用。</p>
<ul>
<li>由于我没使用过这个,所以暂时不清楚优缺点,网上知道的好像也比较少,有使用过的小伙伴可以讨论一下。</li>
</ul>
<h3 id="持续集成持续交付">持续集成/持续交付</h3>
<p>首先我们要了解持续集成(CI:Continuous Integration)/持续交付(CD:Continuous Delivery)是什么东西。持续集成与持续交付是软件开发和交付中的实践。其实就是一个自动化的过程,通过配置一些信息,让系统自动帮我们完成代码质量检查、单元测试、基本BUG查找、编译新版本、自动发布等等。简化工作量的同时又规范了开发流程,可以帮助我们做出更好的产品。</p>
<ul>
<li><strong>持续集成</strong>:软件开发中,集成是一个很可能发生未知错误的过程。持续集成是一种软件开发实践,希望团队中的成员频繁提交代码到代码仓库,且每次提交都能通过自动化测试进行验证,从而使问题尽早暴露和解决。持续集成无法消除bug,但却能大大降低修复的难度和时间。</li>
<li><strong>持续交付</strong>:持续交付指的是,频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。这里需要注意的是,CD代表持续交付(Continuous Delivery)而不是持续部署(Continuous Deploy),因为部署也包括部署到测试环境,而持续交付代表的是功能的上线,交付给用户使用。</li>
<li><strong>持续部署</strong>:持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。持续部署的目标是,代码在任何时刻都是可部署的,可以进入生产阶段。持续部署的前提是能自动化完成测试、构建、部署等步骤。</li>
</ul>
<h4 id="gitlab-1">Gitlab</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-16-55-09.png" alt="" /></p>
<ul>
<li>官网:<a href="https://docs.gitlab.com/ee/ci/">https://docs.gitlab.com/ee/ci/</a></li>
</ul>
<p>GitLab CI / CD是内置在GitLab中的功能强大的工具,它使您可以将所有连续方法(连续集成,交付和部署)应用于软件,而无需第三方应用程序或集成。将.gitlab-ci.yml配置文件添加到存储库后,GitLab将检测到该文件并使用名为GitLab Runner的工具运行脚本,该工具的工作原理与终端类似。</p>
<ul>
<li>优点:如果使用gitlab做代码管理的话,那这就是最佳工具了,毕竟内部集成,整体上品牌机用起来总是比组装机舒服的。</li>
<li>缺点:和上面gitlab一样,耗资源,以前在公司的时候就经历过一次中午的时候公司运维停服加内存的情况。所以,如果不缺钱的话那gitlab全家桶绝对是最佳选择。</li>
</ul>
<h4 id="jenkins">jenkins</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-17-03-55.png" alt="" /></p>
<ul>
<li>官网:<a href="https://www.jenkins.io/">https://www.jenkins.io/</a></li>
<li>开源地址:<a href="https://github.com/jenkinsci/jenkins">https://github.com/jenkinsci/jenkins</a></li>
</ul>
<p>Jenkins是一款由Java编写的开源的持续集成工具。在与Oracle发生争执后,项目从Hudson项目复刻。Jenkins提供了软件开发的持续集成服务。它运行在Servlet容器中(例如Apache Tomcat)。它支持软件配置管理(SCM)工具(包括AccuRev SCM、CVS、Subversion、Git、Perforce、Clearcase和RTC),可以执行基于Apache Ant和Apache Maven的项目,以及任意的Shell脚本和Windows批处理命令。Jenkins的主要开发者是川口耕介。Jenkins是在MIT许可证下发布的自由软件。可以通过各种手段触发构建。例如提交给版本控制系统时被触发,也可以通过类似Cron的机制调度,也可以在其他的构建已经完成时,还可以通过一个特定的URL进行请求。</p>
<ul>
<li>优点:开源免费、跨平台、插件多,一般和Java项目有较好的配合。</li>
<li>缺点:上古时代的页面UI,极其丑陋,配置项较多,好像不支持文件配置。</li>
</ul>
<h4 id="drone">Drone</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-17-11-03.png" alt="" /></p>
<ul>
<li>官网:<a href="https://drone.io/">https://drone.io/</a></li>
<li>开源地址:<a href="https://github.com/drone">https://github.com/drone</a></li>
</ul>
<p>Drone是现代化的持续集成和持续交付平台,可让繁忙的团队自动化其构建,测试和发布工作流程。利用Drone的团队可以更频繁地发布软件,并且漏洞更少。Drone的0.8版本和1.0版本差距很大,包括界面、语法等都有很大差异,尽量使用新版。</p>
<ul>
<li>优点:开源免费,现代化界面,支持ssh、docker、k8s等多种cicd工作方式。可以和github、gitea等多种git服务器进行集成。并且支持插件模式,官网提供了一些常用的服务插件,可以很好的进行CI/CD工作。</li>
<li>缺点:除了常用的CI/CD功能之外,附件功能太少,连成员授权都是基于git的。</li>
</ul>
<h4 id="flowci">flow.ci</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-17-18-18.png" alt="" /></p>
<ul>
<li>官网:<a href="https://flow.ci/">https://flow.ci/</a></li>
<li>开源地址:<a href="https://github.com/flowci">https://github.com/flowci</a></li>
</ul>
<p>flow.ci 使用 Java 语言编写,开源跨平台,是国内目前在做的开源CI/CD。我没用过,不过看界面好像也挺好的。之前停更过一段时间,目前又开启了更新,继续观察吧。</p>
<h4 id="gocd">gocd</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-17-24-49.png" alt="" /></p>
<ul>
<li>官网:<a href="https://www.gocd.org/">https://www.gocd.org/</a></li>
<li>开源地址:<a href="https://github.com/gocd/gocd">https://github.com/gocd/gocd</a></li>
</ul>
<p>GoCD,一款先进的持续集成和发布管理系统,由ThoughtWorks开发。其前身为CruiseControl,是ThoughtWorks在做咨询和交付交付项目时自己开发的一款开源的持续集成工具。后来随着持续集成及持续部署的火热,ThoughtWorks专门成立了一个项目组,基于Cruise开发除了Go这款工具。ThoughtWorks开源持续交付工具Go。使用Go来建立起一个项目的持续部署pipeline是非常快的,非常方便。</p>
<ul>
<li>
<p>优点:开源免费,使用PipeLineGroup,PipeLine,Stage,Job,Task 分级分层控制任务粒度和关联性、强大的用户,角色系统、go-server <–> go-agent 通信和管理模式、除了JRE 1.6+ 以外不依赖其它组件,对系统的冲击很小,方便部署</p>
</li>
<li>
<p>缺点:不支持一个PipeLine、Job在多个Agent上依次执行(对于大规模集群式部署的应用来说,这简直要命)、插件比较稀少、开源时间短,用户群还比较小。</p>
</li>
</ul>
<h3 id="团队协作">团队协作</h3>
<h4 id="yapi">YApi</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-17-49-58.png" alt="" /></p>
<ul>
<li>官网:<a href="https://hellosean1025.github.io/yapi/">https://hellosean1025.github.io/yapi/</a></li>
<li>开源地址:<a href="https://github.com/ymfe/yapi">https://github.com/ymfe/yapi</a></li>
</ul>
<p>去哪儿网开发的Api管理系统,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API。在前后端分离的项目中,可以方便前端查看接口、mock数据,也有利于前后端协作。</p>
<h4 id="ydoc">YDoc</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-17-52-48.png" alt="" /></p>
<ul>
<li>官网:<a href="https://hellosean1025.github.io/ydoc/">https://hellosean1025.github.io/ydoc/</a></li>
<li>开源地址:<a href="https://github.com/ymfe/ydoc">https://github.com/ymfe/ydoc</a></li>
</ul>
<p>同YApi一样,是去哪儿网开发的文档管理系统,基于 markdown 轻松生成完整静态站点。</p>
<h4 id="showdoc">Showdoc</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-17-54-29.png" alt="" /></p>
<ul>
<li>官网:<a href="https://www.showdoc.cc/">https://www.showdoc.cc/</a></li>
<li>开源地址:<a href="https://github.com/star7th/showdoc">https://github.com/star7th/showdoc</a></li>
</ul>
<p>ShowDoc也是一款适合IT团队在线共享文档的工具。它可以提高团队成员之间的沟通效率。</p>
<h4 id="pearproject">pearProject</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-17-56-38.png" alt="" /></p>
<ul>
<li>官网:<a href="https://home.vilson.xyz/">https://home.vilson.xyz/</a></li>
<li>开源地址:<a href="https://github.com/a54552239/pearProject">https://github.com/a54552239/pearProject</a></li>
</ul>
<p>轻量级的在线项目/任务协作系统,远程办公协作。相当于增强版的任务看板、工单系统。</p>
<h3 id="在线ide">在线IDE</h3>
<h4 id="code-server">code-server</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-18-01-29.png" alt="" /></p>
<ul>
<li>官网:<a href="https://coder.com/">https://coder.com/</a></li>
<li>开源地址:<a href="https://github.com/cdr/code-server">https://github.com/cdr/code-server</a></li>
</ul>
<p>这个项目得益于VSCode的发展与开源,虽然微软官方推出了VSCode-Online,但是目前还不支持私有化部署,这个项目就是第三方基于VSCode发展来。旨在在任何地方的任何计算机上运行VS Code,然后在浏览器中对其进行访问。这样我们就可以随时随地打开浏览器进行项目开发啦。只需要有浏览器即可,环境都是一样的,妈妈再也不用担心我在其他地方没有开发环境的问题啦。</p>
<h4 id="jupyterlab">jupyterlab</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-18-06-58.png" alt="" /></p>
<ul>
<li>官网:<a href="https://jupyter.org/">https://jupyter.org/</a></li>
<li>开源地址:<a href="https://github.com/jupyterlab/jupyterlab">https://github.com/jupyterlab/jupyterlab</a></li>
</ul>
<p>一个基于Jupyter Notebook和Architecture的可扩展环境,用于交互式和可重复计算。目前为用户准备。JupyterLab是Jupyter 项目的下一代用户界面,它以灵活而强大的用户界面提供了经典Jupyter Notebook的所有熟悉的构造块(笔记本,终端,文本编辑器,文件浏览器,丰富的输出等)。JupyterLab最终将取代经典的Jupyter Notebook。</p>
<p>这个项目适合用Python来做科学计算的科研人员。</p>
<h3 id="内网穿透">内网穿透</h3>
<h4 id="frp">frp</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-18-10-59.png" alt="" /></p>
<ul>
<li>开源地址:<a href="https://github.com/fatedier/frp">https://github.com/fatedier/frp</a></li>
</ul>
<p>frp是一种快速反向代理,可帮助您将NAT或防火墙后面的本地服务器公开到Internet。到目前为止,它支持TCP和UDP以及HTTP和HTTPS协议,在这些协议中,请求可以通过域名转发到内部服务。frp还具有P2P连接模式。</p>
<p>平时我们自己本地写的项目如果要给别人预览的话是很麻烦的,毕竟本地一般都是内网,外网是没法直接访问的。因此,我们可以在公网上部署frp服务,然后利用公网服务器进行流量中转从而实现外网访问本地项目。特别是小程序的调试,这个是非常有用的。</p>
<h4 id="nps">nps</h4>
<p><img src="/resources/images/2020-06-06/2020-06-06-18-14-58.png" alt="" /></p>
<ul>
<li>官网:<a href="https://ehang.io/nps/documents">https://ehang.io/nps/documents</a></li>
<li>开源地址:<a href="https://github.com/ehang-io/nps">https://github.com/ehang-io/nps</a></li>
</ul>
<p>支持tcp,udp,socks5,http等几乎所有流量转发,可以访问内部网网站,本地支付接口调试,ssh访问,远程桌面的一个轻量级,高性能,功能强大的内网扩展代理服务器。 ,内网dns解析,内网socks5代理等等……,并具有功能强大的内网渗透代理服务器,并具有功能强大的网络管理终端。</p>
<p>功能和frp是一样的,但是frp只支持文件配置访问,而这个工具支持web界面配置访问。有时候文件配置比较舒服,毕竟就那么几行,但是对于新手来说,有个界面让你点点点也是很友好的,各自权衡吧。</p>
<p>内网穿透其实还有很多,这两个是目前比较流行的两个。</p>
<p>参考资料:</p>
<ul>
<li>https://zh.wikipedia.org/</li>
<li>https://baike.baidu.com/</li>
<li>http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html</li>
<li>https://www.jianshu.com/p/67f345ebec9b</li>
</ul>woodcoding 平时自己有写一些小项目,但是写小项目的过程中如果能有一些好用的工具来提升效率的话就最好了。比如自动化测试、自动发布、代码自动检查等功能。今天就来谈谈我自己在用或者接触过的一些工具。Java简明学习笔记2020-05-25T20:30:00+00:002020-05-25T20:30:00+00:00http://blog.woodcoding.com/java/2020/05/25/java-basic<p> JAVA基础系列,包括:</p>
<ul class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />数据类型</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />流程控制与数组</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />面向对象</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />基础类库</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />异常</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />注解</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />反射</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />输入输出流</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />多线程</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />网络编程</li>
</ul>
<!-- more -->
<h3 id="数据类型">数据类型</h3>
<h4 id="标识符命名">标识符命名</h4>
<p>Java标识符必须以字母、下划线(_)、美元符($)开头,后面可以跟任意数目的字母、数字、下划线(_)和美元符。</p>
<h4 id="java关键字">Java关键字</h4>
<table>
<thead>
<tr>
<th>类型</th>
<th>关键字</th>
</tr>
</thead>
<tbody>
<tr>
<td>访问控制</td>
<td>private、protected、public</td>
</tr>
<tr>
<td>类,方法和变量修饰符</td>
<td>abstract、class、extends、final、implements、interface、native、new、static、strictfp、synchronized、transient、volatile</td>
</tr>
<tr>
<td>程序控制</td>
<td>break、continue、return、do、while、if、else、for、instanceof、switch、case、default</td>
</tr>
<tr>
<td>错误处理</td>
<td>try、catch、throw、throws</td>
</tr>
<tr>
<td>包相关</td>
<td>import、package</td>
</tr>
<tr>
<td>基本类型</td>
<td>boolean、byte、char、double、float、int、long、short、null、true、false</td>
</tr>
<tr>
<td>变量引用</td>
<td>super、this、void</td>
</tr>
<tr>
<td>保留字</td>
<td>goto、const</td>
</tr>
</tbody>
</table>
<h4 id="基本数据类型">基本数据类型</h4>
<ol>
<li>整型
<ul>
<li>byte: 内存占8位,表示范围-128~127</li>
<li>short: 内存占16位,表示范围-32768-32767</li>
<li>int: 占32位,表示$-2^{31}$ - $2^{31}-1$,默认定义的数字是int</li>
<li>long:占64位,表示$-2^{63}$ - $2^{63}-1$</li>
</ul>
</li>
<li>字符型
<ul>
<li>char:Java使用16位的Unicode字符作为编码方式,也就是2字节,使用单引号(‘)包含。</li>
</ul>
<p>Java并没有提供表示字符串的基本类型,所以字符串使用String类来表示,使用双引号(“)表示。</p>
</li>
<li>浮点型
<ul>
<li>float: 单精度浮点型,占4字节,32位</li>
<li>double: 双精度浮点型,占8字节,64位</li>
</ul>
</li>
<li>
<p>数值下划线分隔
Java7引入的新功能,数字可以使用下划线进行分隔,便于直观看出有多少位。如:<code class="language-plaintext highlighter-rouge">3.14_15_92_654</code></p>
</li>
<li>布尔型
boolean: 表示逻辑上的”真”或”假”。数值只能是true或者false,不能用0和1表示,其他类型的数据也不能转化成boolean类型。虽然boolean只需要一位就可以表示了,但是由于计算机通常使用8位作为内存最小分配单位,所以大部分情况下占用还是8位。</li>
</ol>
<h3 id="流程控制与数组">流程控制与数组</h3>
<h4 id="流程控制">流程控制</h4>
<ol>
<li>if-else结构</li>
<li>switch分支结构,如:
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">switch</span> <span class="o">(</span><span class="n">expression</span><span class="o">){</span>
<span class="k">case</span> <span class="nl">condition1:</span><span class="o">{</span>
<span class="n">func</span><span class="o">(</span><span class="n">condition1</span><span class="o">);</span>
<span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">case</span> <span class="nl">condition2:</span><span class="o">{</span>
<span class="n">func</span><span class="o">(</span><span class="n">condition2</span><span class="o">);</span>
<span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">default</span><span class="o">:{</span>
<span class="n">func</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>while结构,如:
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span><span class="o">(</span><span class="n">expression</span><span class="o">){</span>
<span class="n">func</span><span class="o">(</span><span class="n">expression</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>do-while结构,如:
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">do</span><span class="o">{</span>
<span class="n">func</span><span class="o">();</span>
<span class="o">}</span><span class="k">while</span><span class="o">(</span><span class="n">expression</span><span class="o">);</span>
</code></pre></div> </div>
</li>
<li>for循环结构,如:
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span><span class="n">i</span><span class="o"><</span><span class="mi">10</span><span class="o">;</span><span class="n">i</span><span class="o">++){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
</ol>
<h4 id="数组">数组</h4>
<ol>
<li>
<p>定义</p>
<p>Java定义数组时,以下两种定义方式都支持:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">type</span><span class="o">[]</span> <span class="n">arrayName</span><span class="o">;</span> <span class="c1">//推荐使用,语义清晰,引用类型</span>
<span class="n">tpye</span> <span class="n">arrayName</span><span class="o">[];</span>
</code></pre></div> </div>
</li>
<li>
<p>初始化</p>
<ul>
<li>静态初始化</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">intArr</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[]{</span><span class="mi">5</span><span class="o">,</span> <span class="mi">6</span><span class="o">,</span> <span class="mi">7</span><span class="o">,</span> <span class="mi">8</span><span class="o">}</span>
</code></pre></div> </div>
<ul>
<li>动态初始化</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">intArr</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="mi">5</span><span class="o">];</span>
</code></pre></div> </div>
</li>
<li>
<p>使用</p>
<ul>
<li>获取长度: arr.length</li>
<li>foreach循环:
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span><span class="o">(</span><span class="nc">String</span> <span class="n">book</span> <span class="o">:</span> <span class="n">books</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">books</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
</ul>
</li>
<li>
<p>Java8增强的工具类Arrays</p>
<p>Java提供的Arrays类包含了一些static修饰的方法可以直接操作数组,详情可以自己看文档。</p>
</li>
</ol>
<h3 id="面向对象上">面向对象(上)</h3>
<h4 id="类对象">类对象</h4>
<p>Java中定义类和C++差不多,类似如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Person</span><span class="o">{</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="n">age</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">say</span><span class="o">(</span><span class="nc">String</span> <span class="n">content</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">content</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>注意:</strong> Java中一个.java文件只能拥有一个public的类。对于各种修饰符类的访问权限在后面进行介绍。</p>
<h4 id="对象引用指针">对象、引用、指针</h4>
<p>Java中使用对象如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">){</span>
<span class="nc">Person</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Person</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>注意到代码中的p变量和Person对象,p变量其实是个引用,这个变量存储在栈中,而真正的对象Person存在堆中,p变量是对Person对象的引用,这和其他语言是一致的。引用和指针很像、但是引用和指针有区别(注意Java中的null很特殊,虽然可以使用p=null这种语法,但是并不能说p为空,关于Java中null的详细定义网上议论纷纷,还需继续深入学习):</p>
<ul>
<li>引用不可用为空,指针可以为空。</li>
<li>引用在创建时必须初始化,引用到一个有效对象,而指针在定义时不用。</li>
<li>引用初始化以后不能被改变,指针可以改变所指的对象。</li>
</ul>
<p>通过this引用可以在类的内部引用本身。</p>
<h4 id="局部变量初始化">局部变量初始化</h4>
<p>Java中可以使用<code class="language-plaintext highlighter-rouge">{}</code>对类的成员变量进行初始化。在定义一个局部变量的时候系统并不会为之分配内存,只有赋值后系统才会分配内存。</p>
<h4 id="访问控制">访问控制</h4>
<pre><code class="language-mermaid">graph LR;
id1([priavate]) --> id2([default])
id2 --> id3([protected])
id3 --> id4([public])
</code></pre>
<p><strong>注意</strong> 访问级别由小到大。</p>
<p>访问控制级别表</p>
<table>
<thead>
<tr>
<th> </th>
<th>private</th>
<th>default</th>
<th>protected</th>
<th>public</th>
</tr>
</thead>
<tbody>
<tr>
<td>同一个类中</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>同一个包中</td>
<td>-</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>子类中</td>
<td>-</td>
<td>-</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>全局范围内</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>√</td>
</tr>
</tbody>
</table>
<h4 id="包管理">包管理</h4>
<p>pacakge、import和import static</p>
<p>Java中允许使用pacakge对一组相同功能进行管理,但是要满足两个条件:</p>
<ul>
<li>源文件使用package语句指定包名。</li>
<li>class文件必须放在对应的目录下。</li>
</ul>
<p>使用import语句可从其他包中导入类,使用import static可从其他包的类中导入静态方法或者静态属性。</p>
<h4 id="构造器">构造器</h4>
<p>Java构造器和C++也类似。不过要注意,构造器代码进行之前,系统已经为对象分配好内存空间了,只是暂时还不能访问而已,构造器只是可以对这个对象进行初始化操作而已。使用this关键字可以在重载的构造器中调用另一个构造器,并且<strong>必须作为构造器执行体第一条语句</strong>。</p>
<h4 id="继承">继承</h4>
<p>使用extends关键字进行类的继承,而且只能单继承,如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">Fruit</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">info</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"我是水果"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Apple</span> <span class="kd">extends</span> <span class="nc">Fruit</span><span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">info</span><span class="o">(){</span>
<span class="kd">super</span><span class="o">.</span><span class="na">info</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"我是苹果"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>使用<code class="language-plaintext highlighter-rouge">super</code>关键字可以使用父类的资源。当程序创建子类时,如果存在同名的实例变量,则系统会为他们都分配内存,而不是只分配一份。</p>
<h4 id="多态">多态</h4>
<p>Java引用变量有两个类型,一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定。运行时类型由实际赋值的类型决定。因此就有可能出现多态。比如Dog、Cat都继承了Animal。则声明以下代码时:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">Animal</span> <span class="n">dog</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Dog</span><span class="o">();</span>
<span class="nc">Animal</span> <span class="n">cat</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Cat</span><span class="o">();</span>
<span class="n">dog</span><span class="o">.</span><span class="na">say</span><span class="o">();</span>
<span class="n">cat</span><span class="o">.</span><span class="na">say</span><span class="o">();</span>
</code></pre></div></div>
<p>由于猫狗的叫声不一样,所以可能出现“汪汪汪”和“喵喵喵”的区别。</p>
<p><strong>注意</strong> 使用instanceof可以判断对象是否是某种类型,方便正确进行多态转换。只要是类型相同或者具有继承关系则返回true。如:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">hello</span> <span class="k">instanceof</span> <span class="nc">Animal</span>
</code></pre></div></div>
<h3 id="面向对象下">面向对象(下)</h3>
<h4 id="java8增强的包装类">Java8增强的包装类</h4>
<p>JDK1.5开始提供了自动装箱和自动拆箱的功能。Java中对8种基本数据类型提供了包装类。如下:</p>
<table>
<thead>
<tr>
<th>基本数据类型</th>
<th>包装类</th>
</tr>
</thead>
<tbody>
<tr>
<td>byte</td>
<td>Byte</td>
</tr>
<tr>
<td>short</td>
<td>Short</td>
</tr>
<tr>
<td>int</td>
<td>Integer</td>
</tr>
<tr>
<td>long</td>
<td>Long</td>
</tr>
<tr>
<td>char</td>
<td>Character</td>
</tr>
<tr>
<td>float</td>
<td>Float</td>
</tr>
<tr>
<td>double</td>
<td>Double</td>
</tr>
<tr>
<td>boolean</td>
<td>Boolean</td>
</tr>
</tbody>
</table>
<h4 id="对象比较">对象比较</h4>
<p>由于包装类对象其实是引用类型,所以以下代码会输出false:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="k">new</span> <span class="nc">Integer</span><span class="o">(</span><span class="mi">2</span><span class="o">)==</span><span class="k">new</span> <span class="nc">Integer</span><span class="o">(</span><span class="mi">2</span><span class="o">));</span>
</code></pre></div></div>
<p>但是由于Java可以自动装箱,以下代码会输出true:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Integer</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">127</span><span class="o">;</span>
<span class="nc">Integer</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">127</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span> <span class="o">==</span> <span class="n">b</span><span class="o">);</span>
</code></pre></div></div>
<p>因为在Java中对-128–127做了缓存,所以a和b其实是一个对象。同理也可以应用到Java的字符串对象,Java对字符串常量做了常量池管理,所以如果是静态定义相同的字符串值是相等的,但是字符串运算或者new出来的即使值相等,==也是false。如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">s0</span> <span class="o">=</span> <span class="s">"Java学习"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">s1</span> <span class="o">=</span> <span class="s">"Java学习"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">s2</span> <span class="o">=</span> <span class="s">"Java"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">s3</span> <span class="o">=</span> <span class="s">"学习"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">s4</span> <span class="o">=</span> <span class="n">s2</span> <span class="o">+</span> <span class="n">s3</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s4</span><span class="o">==</span><span class="n">s1</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s0</span><span class="o">==</span><span class="n">s1</span><span class="o">);</span>
</code></pre></div></div>
<p>所以要想比较两个值相等,应该使用equals方法。</p>
<h4 id="final修饰符">final修饰符</h4>
<p>final修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值。而且final修饰的成员必须由程序员显式赋值。final修饰的成员不可重新赋值,但是如果改成员是一个引用对象,那么引用对象的成员是可以改变的。final在Java中的作用其实相当于C++中的“宏定义”。下面我们来看一个例子:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">s1</span> <span class="o">=</span> <span class="s">"Java学习"</span><span class="o">;</span>
<span class="kd">final</span> <span class="nc">String</span> <span class="n">s2</span> <span class="o">=</span> <span class="s">"Java"</span><span class="o">;</span>
<span class="kd">final</span> <span class="nc">String</span> <span class="n">s3</span> <span class="o">=</span> <span class="s">"学习"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">s4</span> <span class="o">=</span> <span class="n">s2</span> <span class="o">+</span> <span class="n">s3</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s4</span><span class="o">==</span><span class="n">s1</span><span class="o">);</span>
</code></pre></div></div>
<p>代码和上面的差不多,区别就是给s2和s3加上了final修饰的关键字,现在执行,你会发现s4和s1输出的是true而不是false。这是因为final修饰的值是编译时就已经确定的,所以会纳入常量池。</p>
<h4 id="抽象类">抽象类</h4>
<p>先看一个抽象类的例子:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">JavaAbstract</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">abstract</span> <span class="kt">void</span> <span class="nf">go</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">AbstractImpl</span> <span class="kd">extends</span> <span class="nc">JavaAbstract</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">go</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Go!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Java中的抽象类使用abstract关键对抽象类进行定义。抽象类可以理解为一个模板,可以提供一些已经实现的方法,以及定义一些必须实现的抽象方法。这个模板不能被实例化,必须要被其他类继承并实现对应的抽象方法后才能进行实例化。由于abstract必须被继承实现,因此他和final、static、private有一些互斥关系,这个需要想清楚。</p>
<h4 id="接口">接口</h4>
<p>先看一个接口的例子:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">JavaInterface</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">();</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">go</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">InterfaceImpl</span> <span class="kd">implements</span> <span class="nc">JavaInterface</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"test interface"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">go</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"go interface"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Java中的接口使用interface关键字进行定义。接口可以理解为规范,这和抽象类的模板不同。相当于电脑上有内存条插槽,你只要实现了相关的内存条制造规范,那你就能插上电脑使用。这个主要是弥补Java单继承的不足,因为接口可以同时实现多个。这里放一下接口和抽象类的区别:</p>
<ul>
<li>接口只能包含抽象方法和默认方法,而抽象类可以提供普通方法(至于默认方法和普通方法的区别?感觉这个默认方法就是个大坑,其实是因为Java中一些接口更新导致新版本编译不过的而提供的一个补救措施,但是却带来了多继承的问题,详情请自己了解,主要就是记住,少考虑默认方法,多继承下类方法优先)。</li>
<li>接口里不能定义静态方法,抽象类可以(书上是这么说的,但是Java8增强了,接口可以定义static方法,做加法真是害死人啊)。</li>
<li>接口中定义的成员属性都是静态的,而抽象类可以提供普通变量。</li>
<li>接口不含构造器,抽象类可以。</li>
<li>接口不能有初始化语句块,抽象类可以。</li>
<li>接口可以多继承,抽象类只能单继承。</li>
</ul>
<h4 id="内部类">内部类</h4>
<p>可以在内的内部再定义类,主要是为了更好的封装,如果只是一次性使用的类可以使用内部类进行定义。内部类中使用<code class="language-plaintext highlighter-rouge">外部类名.this.varName</code>对外部类进行访问。</p>
<h4 id="lambda表达式">Lambda表达式</h4>
<p>Lambda表达式是Java8的重要更新,其实就是用更简洁的代码来创建只有一个抽象方法的接口(又称之为函数接口)。相比起Py的lambda表达式,这个语法真是太尴尬了。例如:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Eatable</span> <span class="n">obj</span> <span class="o">=</span> <span class="o">()</span> <span class="o">-></span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"hh"</span><span class="o">);</span>
<span class="o">};</span>
<span class="n">obj</span><span class="o">.</span><span class="na">eat</span><span class="o">();</span>
</code></pre></div></div>
<h4 id="枚举类">枚举类</h4>
<p>和其他语言都差不多,直接举例吧:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="nc">Gender</span><span class="o">{</span>
<span class="no">MALE</span><span class="o">(</span><span class="s">"男"</span><span class="o">),</span>
<span class="no">FEMALE</span><span class="o">(</span><span class="s">"女"</span><span class="o">);</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nf">Gender</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">){</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">(){</span>
<span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="基础类库">基础类库</h3>
<h4 id="基本库">基本库</h4>
<p>Java的基础类库都是一些方法的调用,需要手动实践理解。其中提供了包括系统相关(System、Runtime等)、字符串相关(String、StringBuffer、StringBuilder等)、对象(Object)、数学(Math、BigDecimal等)、日期(Date、Calendar等)、正则、国际化等等。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JavaBasicUtils</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Runtime</span> <span class="n">rt</span> <span class="o">=</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">getRuntime</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">rt</span><span class="o">.</span><span class="na">availableProcessors</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">rt</span><span class="o">.</span><span class="na">freeMemory</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">rt</span><span class="o">.</span><span class="na">maxMemory</span><span class="o">());</span>
<span class="nc">Random</span> <span class="n">rd1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Random</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">());</span>
<span class="nc">Random</span> <span class="n">rd2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Random</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">rd1</span><span class="o">.</span><span class="na">nextInt</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">rd2</span><span class="o">.</span><span class="na">nextInt</span><span class="o">());</span>
<span class="nc">ThreadLocalRandom</span> <span class="n">tlr1</span> <span class="o">=</span> <span class="nc">ThreadLocalRandom</span><span class="o">.</span><span class="na">current</span><span class="o">();</span>
<span class="nc">ThreadLocalRandom</span> <span class="n">tlr2</span> <span class="o">=</span> <span class="nc">ThreadLocalRandom</span><span class="o">.</span><span class="na">current</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">tlr1</span><span class="o">.</span><span class="na">nextInt</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">tlr2</span><span class="o">.</span><span class="na">nextInt</span><span class="o">());</span>
<span class="nc">Date</span> <span class="n">d1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">();</span>
<span class="nc">Date</span> <span class="n">d2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()+</span><span class="mi">100</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">d1</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">d2</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">d1</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">d2</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">d1</span><span class="o">.</span><span class="na">before</span><span class="o">(</span><span class="n">d2</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Locale</span><span class="o">.</span><span class="na">Category</span><span class="o">.</span><span class="na">FORMAT</span><span class="o">);</span>
<span class="c1">// Locale myLocale = Locale.getDefault(Locale.Category.FORMAT);</span>
<span class="nc">Locale</span> <span class="n">myLocale</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Locale</span><span class="o">(</span><span class="s">"en"</span><span class="o">,</span> <span class="s">"US"</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">myLocale</span><span class="o">);</span>
<span class="nc">ResourceBundle</span> <span class="n">bundle</span> <span class="o">=</span> <span class="nc">ResourceBundle</span><span class="o">.</span><span class="na">getBundle</span><span class="o">(</span><span class="s">"mess"</span><span class="o">,</span> <span class="n">myLocale</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">bundle</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"hello"</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">MessageFormat</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="n">bundle</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"welcome"</span><span class="o">),</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">()));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="集合">集合</h4>
<ol>
<li>
<p>概述</p>
<p>Java中集合类型主要由两个接口派生:Collection和Map。详情查看下面的的图:
<img src="/resources/images/2020-06-04/2020-06-04-20-48-04.png" alt="" /></p>
<p><img src="/resources/images/2020-06-04/2020-06-04-20-49-55.png" alt="" /></p>
<p>Collection中定义了一些基本方法,如add、clear、contains等。同时可以使用Iterator迭代器进行元素迭代,如下所示:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">JavaSet</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Collection</span> <span class="n">c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">();</span>
<span class="n">c</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s">"Java"</span><span class="o">);</span>
<span class="n">c</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">100</span><span class="o">);</span>
<span class="n">c</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">100</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">c</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">c</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="mi">100</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">c</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
<span class="n">c</span><span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">obj</span><span class="o">-></span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">obj</span><span class="o">));</span>
<span class="nc">Iterator</span> <span class="n">it</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="na">iterator</span><span class="o">();</span>
<span class="k">while</span> <span class="o">(</span><span class="n">it</span><span class="o">.</span><span class="na">hasNext</span><span class="o">()){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">it</span><span class="o">.</span><span class="na">next</span><span class="o">());</span>
<span class="k">if</span><span class="o">(</span><span class="n">it</span><span class="o">.</span><span class="na">next</span><span class="o">().</span><span class="na">toString</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="s">"Java"</span><span class="o">)){</span>
<span class="n">it</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
<p>同时Java还支持链式操作,如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">books</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">filter</span><span class="o">(</span><span class="n">ele</span><span class="o">->((</span><span class="nc">String</span><span class="o">)</span><span class="n">ele</span><span class="o">).</span><span class="na">contains</span><span class="o">(</span><span class="s">"Java"</span><span class="o">)).</span><span class="na">count</span><span class="o">();</span>
</code></pre></div> </div>
</li>
<li>
<p>Set</p>
<p>Set是一个元素不重复的元组,在Java中有好几种数据结构的实现,根据不同的数据结构可以实现不同的操作。</p>
<ul>
<li>HashSet:经典Set实现,不保证元素排列顺序,非线程安全,元素值可以为null(注意对象的比较,如果要保证自定义对象唯一,需要重写equals和hashCode方法)</li>
<li>LinkedHashSet:从名字可以看出是链表实现的Set,按照添加元素的先后添加元素,因此性能略低于HashSet,但是在迭代访问全部元素时性能很好。</li>
<li>TreeSet:从名字可以看出是树结构实现的Set,非线程安全,由于也是SortedSet接口的实现,所以元素有序,可以有序访问元素以及上一个、下一个等。</li>
<li>EnumSet:专为枚举类设计的集合类,非线程安全。</li>
</ul>
</li>
<li>
<p>List</p>
<ul>
<li>ArrayList是最常用的List实现,数组实现,允许对元素进行快速随机访问,当数组大小不满足时需要增加存储时需要将已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。</li>
<li>Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。但是Vector提供了一个Stack子类,即栈的实现。</li>
<li>LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。</li>
</ul>
</li>
<li>
<p>Queue</p>
<p>队列的实现。一个特别的实现是优先队列(PriorityQueue)。如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">PriorityQueue</span> <span class="n">pq</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PriorityQueue</span><span class="o">();</span>
<span class="n">pq</span><span class="o">.</span><span class="na">offer</span><span class="o">(</span><span class="mi">6</span><span class="o">);</span>
<span class="n">pq</span><span class="o">.</span><span class="na">offer</span><span class="o">(</span><span class="mi">11</span><span class="o">);</span>
<span class="n">pq</span><span class="o">.</span><span class="na">offer</span><span class="o">(</span><span class="mi">8</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">pq</span><span class="o">.</span><span class="na">poll</span><span class="o">());</span>
</code></pre></div> </div>
</li>
<li>
<p>Map</p>
<p>字典的实现,就是key-value的形式。同Set一样,Java中也有多种实现:</p>
<ul>
<li>HashMap与HashTable:HashMap非线程安全,允许null为key,而HashTable则相反,HashMap是比较常用的,保证线程安全可以使用后面讲的Collection类。</li>
<li>LinkedHashMap:主要是基于Hash再加上前后指针的双向链表实现,性能略低于HashMap,但是迭代全部元素时性能挺好,可以保持插入顺序或者访问顺序进行读取。</li>
<li>TreeMap:基于红黑树数据结构实现,可以保证key有序。</li>
<li>WeakHashMap:key对象只持有对实际对象的弱引用,当垃圾回收了key对应的对象后,此key将被删除。</li>
<li>IdentityHashMap:只有当两个key对象完全相等,即==号判断为true时才是相同的key,因此可以添加两个new String(“Java”)到Map中。</li>
<li>EnumMap:顾名思义,只保存枚举类的Map。</li>
</ul>
</li>
<li>
<p>Collections</p>
<p>操作集合的工具类,提供了集合的多种操作:</p>
<ul>
<li>排序操作:reverse、shuffle、sort、swap、rotate等。</li>
<li>查找替换:binarySearch、max、min、fill等。</li>
<li>
<p>同步控制:使用Collections.synchronizedXXX等方法可以让非线程安全的结合变成线程安全,但是通过Iterator、Spliterator或Stream遍历这个新List时,需要在外部做好同步。如:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">List</span> <span class="n">list</span> <span class="o">=</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">synchronizedList</span><span class="o">(</span><span class="k">new</span> <span class="nc">ArrayList</span><span class="o">());</span>
</code></pre></div> </div>
</li>
</ul>
</li>
</ol>
<h3 id="泛型">泛型</h3>
<p>JDK1.5开始增加了对泛型的支持,没有泛型之前,当把一个对象放入集合,集合会忘记这个对象的类型,也就是变成Object,拿出来的时候需要进行显式类型转换,而且容易引起ClassCastException异常。而增加泛型支持之后则不需要显式转换了。</p>
<h4 id="使用泛型">使用泛型</h4>
<p>Java7之前,使用泛型是这样的:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">strList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><</span><span class="nc">String</span><span class="o">>();</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">></span> <span class="n">scores</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">>();</span>
</code></pre></div></div>
<p>Java7之后,可以去掉后面尖括号里面的东西了,毕竟这样显得多余。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">strList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">></span> <span class="n">scores</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
</code></pre></div></div>
<h4 id="定义泛型">定义泛型</h4>
<p>如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Fruit</span><span class="o"><</span><span class="no">T</span><span class="o">>{</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">();</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">add</span><span class="o">(</span><span class="no">T</span> <span class="n">v</span><span class="o">){</span>
<span class="n">data</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">v</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">show</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">data</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>其实就是用了一个<code class="language-plaintext highlighter-rouge"><T></code>来代替我们实例化时所规定的类型。这个T其实可以是任意变量名,但是我们一般为了规范会遵循以下规则:</p>
<ul>
<li>E - Element (在集合中使用,因为集合中存放的是元素)</li>
<li>T - Type(Java 类)</li>
<li>K - Key(键)</li>
<li>V - Value(值)</li>
<li>N - Number(数值类型)</li>
<li>? - 表示不确定的java类型</li>
<li>S、U、V - 2nd、3rd、4th types</li>
</ul>
<p>注意其中的?,可以用来规定继承上限和继承下限。上限通配符 <? extends U> 可以代表这个未知类型 U,或者 通过关键字 extends 所能想象出的 U 类的任何一个子类。同样,下限通配符 <? super L> 可以代表这个未知类型 L,或者 通过关键字 super 所能想象出的 L类的任何一个超类。</p>
<h3 id="异常">异常</h3>
<p>使用try-catch-finally结构进行异常捕获与处理。其中finally是可选的。使用throws关键字可以抛出捕获的异常,并且可以抛出多个。使用throw可以抛出自定义异常,注意和前面throws的区别,throws是抛出已经发生的异常。代码示例如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyException</span> <span class="kd">extends</span> <span class="nc">Exception</span><span class="o">{</span>
<span class="kd">public</span> <span class="nf">MyException</span><span class="o">(){};</span>
<span class="kd">public</span> <span class="nf">MyException</span><span class="o">(</span><span class="nc">String</span> <span class="n">msg</span><span class="o">){</span>
<span class="kd">super</span><span class="o">(</span><span class="n">msg</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">MyException</span><span class="o">(</span><span class="nc">Throwable</span> <span class="n">t</span><span class="o">){</span>
<span class="kd">super</span><span class="o">(</span><span class="n">t</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestException</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span><span class="o">{</span>
<span class="n">firstMethod</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">catch</span> <span class="o">(</span><span class="nc">MyException</span> <span class="n">ex</span><span class="o">){</span>
<span class="n">ex</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">finally</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"测试完毕!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">firstMethod</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">MyException</span> <span class="o">{</span>
<span class="n">secondMethod</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">secondMethod</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">MyException</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">MyException</span><span class="o">(</span><span class="s">"发生异常。。。"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="注解">注解</h3>
<p>Java的注解看起来和Python的装饰器语法一样,其实效果差得远了。Python里面只要装饰起来就可以直接操作这个对象,但是Java这个注解其实就是个标记而已,如果想要操作,还要通过反射来获取对象。如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">//自定义一个注解</span>
<span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span>
<span class="nd">@interface</span> <span class="nc">MyTag</span><span class="o">{</span>
<span class="nc">String</span> <span class="nf">name</span><span class="o">()</span> <span class="k">default</span> <span class="s">"Test"</span><span class="o">;</span>
<span class="kt">int</span> <span class="nf">age</span><span class="o">()</span> <span class="k">default</span> <span class="mi">22</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@MyTag</span>
<span class="kd">class</span> <span class="nc">Test</span><span class="o">{</span>
<span class="nd">@Deprecated</span>
<span class="nd">@MyTag</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">qq</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"gg"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestAnnotation</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ClassNotFoundException</span><span class="o">,</span> <span class="nc">NoSuchMethodException</span> <span class="o">{</span>
<span class="nc">Test</span> <span class="n">tt</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Test</span><span class="o">();</span>
<span class="nc">Annotation</span><span class="o">[]</span> <span class="n">arr</span> <span class="o">=</span> <span class="n">tt</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getMethod</span><span class="o">(</span><span class="s">"qq"</span><span class="o">).</span><span class="na">getAnnotations</span><span class="o">();</span>
<span class="k">for</span><span class="o">(</span><span class="nc">Annotation</span> <span class="n">an</span> <span class="o">:</span> <span class="n">arr</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">an</span><span class="o">);</span>
<span class="k">if</span><span class="o">(</span><span class="n">an</span> <span class="k">instanceof</span> <span class="nc">MyTag</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(((</span><span class="nc">MyTag</span><span class="o">)</span> <span class="n">an</span><span class="o">).</span><span class="na">age</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<ol>
<li>五个基本注解类型</li>
</ol>
<ul>
<li>@Override:复写父类方法注解,就是标记性的,加上这个注解之后编译器会检查是否正确复写,避免出现复写方法错误的问题。</li>
<li>@Deprecated:标记已过时</li>
<li>@SuppressWarnings:抑制编译器警告,就是取消编译器的警告提示。</li>
<li>@SafeVarargs:抑制“堆污染”警告,就是将A类型的泛型对象赋值给B类型的泛型引用。</li>
<li>@FunctionalInterface:函数式接口注解,加上之后就会严格检查只能有一个抽象方法。</li>
</ul>
<ol>
<li>四个元注解</li>
</ol>
<ul>
<li>@Retention:修饰注解定义,指定被修饰的注解的存在方式。有RetentionPolicy.CLASS,默认值,记录在class文件中,运行时不可获取信息、RetentionPolicy.RUNTIME,记录在class文件中,运行时JVM或者反射可以获取到信息、RetentionPolicy.SOURCE,只保留在源代码中,编译时丢弃。</li>
<li>@Target:修饰注解定义,指定被注解修饰的对象的类型,也有很多value值可以取。</li>
<li>@Documented:修饰注解定义,在对象被javadoc提取文档时也提取注解名。</li>
<li>@Inherited:修饰注解定义,指定注解可被继承。</li>
</ul>
<h3 id="反射">反射</h3>
<p>上面的注解已经使用过反射功能了。Java中的反射其实就是在程序运行时从JVM中拿到对象并对对象进行系列操作的过程,这个功能倒是和Python的装饰器功能很像了。Java的反射涉及到JVM一些底层的知识,务必要多多了解。</p>
<h4 id="类加载">类加载</h4>
<p>首先要知道一个Java程序会运行在一个JVM虚拟机中,不管你开了多少个线程,都只有一个JVM进程,这也是接下来我们只学习多线程而没有多进程的原因。当程序使用某个类时会通过加载、连接、初始化三个步骤进行操作。Java有三个基本类加载器,开发者也可以通过继承ClassLoader来实现自己的类加载器,下面是三个基本类加载器的说明:</p>
<ul>
<li>Bootstrap ClassLoader:根类加载器,加载Java核心类,非Java实现</li>
<li>Extension ClassLoader:扩展类加载器,加载ext扩展类,父类加载器是根类加载器,但是.getParet方法并不会返回根类加载器,因为根类加载器非Java实现。</li>
<li>System ClassLoader:系统类加载器,加载CLASSPATH下的类,如无特殊指定,用户自定义的类都以系统类加载器加载。</li>
</ul>
<h4 id="反射操作对象">反射操作对象</h4>
<p>下面是反射操作对象的一个例子</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">PersonReflect</span><span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">age</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">PersonReflect</span><span class="o">(){}</span>
<span class="kd">public</span> <span class="nf">PersonReflect</span><span class="o">(</span><span class="kt">int</span> <span class="n">age</span><span class="o">,</span> <span class="nc">String</span> <span class="n">name</span><span class="o">){</span>
<span class="k">this</span><span class="o">.</span><span class="na">age</span><span class="o">=</span><span class="n">age</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span><span class="o">=</span><span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">show</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"show"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">show</span><span class="o">(</span><span class="nc">String</span> <span class="n">msg</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">msg</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">go</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"go"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestReflect</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchMethodException</span><span class="o">,</span> <span class="nc">InvocationTargetException</span><span class="o">,</span> <span class="nc">IllegalAccessException</span><span class="o">,</span> <span class="nc">InstantiationException</span> <span class="o">{</span>
<span class="nc">Class</span><span class="o"><</span><span class="nc">PersonReflect</span><span class="o">></span> <span class="n">clazz</span> <span class="o">=</span> <span class="nc">PersonReflect</span><span class="o">.</span><span class="na">class</span><span class="o">;</span>
<span class="nc">Constructor</span><span class="o"><?>[]</span> <span class="n">cts</span> <span class="o">=</span> <span class="n">clazz</span><span class="o">.</span><span class="na">getDeclaredConstructors</span><span class="o">();</span>
<span class="k">for</span><span class="o">(</span><span class="nc">Constructor</span> <span class="n">c</span> <span class="o">:</span> <span class="n">cts</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">c</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">Method</span><span class="o">[]</span> <span class="n">mtds</span> <span class="o">=</span> <span class="n">clazz</span><span class="o">.</span><span class="na">getDeclaredMethods</span><span class="o">();</span>
<span class="k">for</span><span class="o">(</span><span class="nc">Method</span> <span class="n">mtd</span> <span class="o">:</span> <span class="n">mtds</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">mtd</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">Method</span> <span class="n">method</span> <span class="o">=</span> <span class="n">clazz</span><span class="o">.</span><span class="na">getMethod</span><span class="o">(</span><span class="s">"show"</span><span class="o">,</span> <span class="nc">String</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">method</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">clazz</span><span class="o">.</span><span class="na">newInstance</span><span class="o">(),</span> <span class="s">"hhh"</span><span class="o">);</span>
<span class="nc">Parameter</span><span class="o">[]</span> <span class="n">parameters</span> <span class="o">=</span> <span class="n">method</span><span class="o">.</span><span class="na">getParameters</span><span class="o">();</span>
<span class="k">for</span><span class="o">(</span><span class="nc">Parameter</span> <span class="n">p</span> <span class="o">:</span> <span class="n">parameters</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="动态代理">动态代理</h4>
<p>由于Java反射提供了一个Proxy的类,我们还可以利用反射特性和Proxy来实现类对象的动态代理,这在AOP面向切面编程中有着重要作用。代理一个很重要的作用就是我们可以在程序执行前后执行一些操作,如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">DogUtil</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">before</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"调用前"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">after</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"调用后"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">interface</span> <span class="nc">Dog</span><span class="o">{</span>
<span class="kt">void</span> <span class="nf">info</span><span class="o">();</span>
<span class="kt">void</span> <span class="nf">run</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">GunDog</span> <span class="kd">implements</span> <span class="nc">Dog</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">info</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Gun Dog"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">(){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Gun Dog run"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">MyInvocationHandler</span> <span class="kd">implements</span> <span class="nc">InvocationHandler</span><span class="o">{</span>
<span class="kd">private</span> <span class="nc">Object</span> <span class="n">target</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setTarget</span><span class="o">(</span><span class="nc">Object</span> <span class="n">obj</span><span class="o">){</span>
<span class="k">this</span><span class="o">.</span><span class="na">target</span> <span class="o">=</span> <span class="n">obj</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Object</span> <span class="nf">invoke</span><span class="o">(</span><span class="nc">Object</span> <span class="n">proxy</span><span class="o">,</span> <span class="nc">Method</span> <span class="n">method</span><span class="o">,</span> <span class="nc">Object</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InvocationTargetException</span><span class="o">,</span> <span class="nc">IllegalAccessException</span> <span class="o">{</span>
<span class="nc">DogUtil</span> <span class="n">du</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DogUtil</span><span class="o">();</span>
<span class="n">du</span><span class="o">.</span><span class="na">before</span><span class="o">();</span>
<span class="nc">Object</span> <span class="n">result</span> <span class="o">=</span> <span class="n">method</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
<span class="n">du</span><span class="o">.</span><span class="na">after</span><span class="o">();</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">MyProxyFactory</span><span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">Object</span> <span class="nf">getProxy</span><span class="o">(</span><span class="nc">Object</span> <span class="n">target</span><span class="o">){</span>
<span class="nc">MyInvocationHandler</span> <span class="n">handler</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MyInvocationHandler</span><span class="o">();</span>
<span class="n">handler</span><span class="o">.</span><span class="na">setTarget</span><span class="o">(</span><span class="n">target</span><span class="o">);</span>
<span class="k">return</span> <span class="nc">Proxy</span><span class="o">.</span><span class="na">newProxyInstance</span><span class="o">(</span><span class="n">target</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getClassLoader</span><span class="o">(),</span> <span class="n">target</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getInterfaces</span><span class="o">(),</span> <span class="n">handler</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestProxy</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Dog</span> <span class="n">target</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GunDog</span><span class="o">();</span>
<span class="nc">Dog</span> <span class="n">dog</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Dog</span><span class="o">)</span><span class="nc">MyProxyFactory</span><span class="o">.</span><span class="na">getProxy</span><span class="o">(</span><span class="n">target</span><span class="o">);</span>
<span class="n">dog</span><span class="o">.</span><span class="na">info</span><span class="o">();</span>
<span class="n">dog</span><span class="o">.</span><span class="na">run</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="输入输出流">输入输出流</h3>
<h4 id="文件访问">文件访问</h4>
<p>通过java.io下的File类,我们可以执行一些文件相关的操作,如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestFile</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">File</span> <span class="n">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="s">"."</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">getParent</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">getAbsoluteFile</span><span class="o">());</span>
<span class="nc">File</span> <span class="n">tmpFile</span> <span class="o">=</span> <span class="nc">File</span><span class="o">.</span><span class="na">createTempFile</span><span class="o">(</span><span class="s">"aaa"</span><span class="o">,</span> <span class="s">".txt"</span><span class="o">,</span> <span class="n">file</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">tmpFile</span><span class="o">.</span><span class="na">exists</span><span class="o">());</span>
<span class="n">tmpFile</span><span class="o">.</span><span class="na">deleteOnExit</span><span class="o">();</span>
<span class="nc">File</span> <span class="n">newFile</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()+</span><span class="s">""</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">newFile</span><span class="o">.</span><span class="na">exists</span><span class="o">());</span>
<span class="c1">// newFile.createNewFile();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">newFile</span><span class="o">.</span><span class="na">exists</span><span class="o">());</span>
<span class="nc">String</span><span class="o">[]</span> <span class="n">fileList</span> <span class="o">=</span> <span class="n">file</span><span class="o">.</span><span class="na">list</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="nl">fileName:</span> <span class="n">fileList</span>
<span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">fileName</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">File</span> <span class="nl">root:</span> <span class="nc">File</span><span class="o">.</span><span class="na">listRoots</span><span class="o">())</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">root</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="流对象">流对象</h4>
<p>Java的IO流是实现输入输出的基础,把硬件、文件等输入输出都包装成Stream流式对象,可以很方便的实现输入输出操作。下面是一个把键盘输入包装成流操作的例子:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestSteam</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">InputStreamReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">);</span>
<span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="n">reader</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">line</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">while</span> <span class="o">((</span><span class="n">line</span><span class="o">=</span><span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">())</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">line</span><span class="o">);</span>
<span class="k">if</span><span class="o">(</span><span class="s">"exit"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">line</span><span class="o">))</span><span class="nc">System</span><span class="o">.</span><span class="na">exit</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="序列化">序列化</h4>
<p>序列化在数据存储和传输方面有着极大的作用。 Java中要实现序列化则类必须实现Serializable、Externalizable两个接口之一。下面是将对象进行序列化的例子:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Person</span> <span class="kd">implements</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">Serializable</span><span class="o">{</span>
<span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">age</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">Person</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">int</span> <span class="n">age</span><span class="o">){</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span><span class="o">=</span><span class="n">name</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">age</span><span class="o">=</span><span class="n">age</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestPickle</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span><span class="o">(</span><span class="nc">ObjectOutputStream</span> <span class="n">oos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectOutputStream</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileOutputStream</span><span class="o">(</span><span class="s">"object.txt"</span><span class="o">))){</span>
<span class="nc">Person</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Person</span><span class="o">(</span><span class="s">"Java"</span><span class="o">,</span> <span class="mi">22</span><span class="o">);</span>
<span class="n">oos</span><span class="o">.</span><span class="na">writeObject</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">FileNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">try</span><span class="o">(</span><span class="nc">ObjectInputStream</span> <span class="n">ois</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectInputStream</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="s">"object.txt"</span><span class="o">))){</span>
<span class="nc">Person</span> <span class="n">p</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Person</span><span class="o">)</span><span class="n">ois</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">FileNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ClassNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="nio新io">NIO新IO</h4>
<p>Java中传统的输入输出流是阻塞式的,nio中将文件或片段映射到内存中可以高效的进行存取访问,但是由于使用方法复杂以及目前还没了解到应用场景所以暂时放一放,后面再细看。</p>
<h3 id="多线程">多线程</h3>
<p>在Java中没有多进程这个说法,只有多线程,因为一个JVM虚拟机就是一个进程。通过使用多线程可以显著提高程序效率,但是也要考虑好线程间的同步互斥问题。</p>
<h4 id="使用线程的三种方法">使用线程的三种方法</h4>
<ol>
<li>
<p>继承Thread类
通过继承Thread类并重写其中的run方法。然后new这个对象执行start方法即可。如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">FirstThread</span> <span class="kd">extends</span> <span class="nc">Thread</span>
<span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">i</span> <span class="o">;</span>
<span class="c1">// 重写run方法,run方法的方法体就是线程执行体</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span>
<span class="o">{</span>
<span class="k">for</span> <span class="o">(</span> <span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span> <span class="o">;</span> <span class="n">i</span><span class="o">++</span> <span class="o">)</span>
<span class="o">{</span>
<span class="c1">// 当线程类继承Thread类时,直接使用this即可获取当前线程</span>
<span class="c1">// Thread对象的getName()返回当前该线程的名字</span>
<span class="c1">// 因此可以直接调用getName()方法返回当前线程的名</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span>
<span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span>
<span class="o">{</span>
<span class="c1">// 调用Thread的currentThread方法获取当前线程</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getName</span><span class="o">()</span>
<span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">20</span><span class="o">)</span>
<span class="o">{</span>
<span class="c1">// 创建、并启动第一条线程</span>
<span class="k">new</span> <span class="nf">FirstThread</span><span class="o">().</span><span class="na">start</span><span class="o">();</span>
<span class="c1">// 创建、并启动第二条线程</span>
<span class="k">new</span> <span class="nf">FirstThread</span><span class="o">().</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p>实现Runable接口
通过实现Runable接口的run方法,实例化对象后作为target传入Thread开启新线程。如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SecondThread</span> <span class="kd">implements</span> <span class="nc">Runnable</span>
<span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">i</span> <span class="o">;</span>
<span class="c1">// run方法同样是线程执行体</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span>
<span class="o">{</span>
<span class="k">for</span> <span class="o">(</span> <span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span> <span class="o">;</span> <span class="n">i</span><span class="o">++</span> <span class="o">)</span>
<span class="o">{</span>
<span class="c1">// 当线程类实现Runnable接口时,</span>
<span class="c1">// 如果想获取当前线程,只能用Thread.currentThread()方法。</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getName</span><span class="o">()</span>
<span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span>
<span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span>
<span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getName</span><span class="o">()</span>
<span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">20</span><span class="o">)</span>
<span class="o">{</span>
<span class="nc">SecondThread</span> <span class="n">st</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SecondThread</span><span class="o">();</span> <span class="c1">// ①</span>
<span class="c1">// 通过new Thread(target , name)方法创建新线程</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(</span><span class="n">st</span> <span class="o">,</span> <span class="s">"新线程1"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(</span><span class="n">st</span> <span class="o">,</span> <span class="s">"新线程2"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p>实现Callable接口
这种方法和上面的Runable差不多,但是这种方法可以有返回值。如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ThirdThread</span> <span class="kd">implements</span> <span class="nc">Callable</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span>
<span class="o">{</span>
<span class="c1">// 实现call方法,作为线程执行体</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">call</span><span class="o">()</span>
<span class="o">{</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span> <span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span> <span class="o">;</span> <span class="n">i</span><span class="o">++</span> <span class="o">)</span>
<span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getName</span><span class="o">()</span>
<span class="o">+</span> <span class="s">" 的循环变量i的值:"</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// call()方法可以有返回值</span>
<span class="k">return</span> <span class="n">i</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span>
<span class="o">{</span>
<span class="c1">// 创建Callable对象</span>
<span class="nc">ThirdThread</span> <span class="n">rt</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ThirdThread</span><span class="o">();</span>
<span class="c1">// 使用FutureTask来包装Callable对象</span>
<span class="nc">FutureTask</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">task</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FutureTask</span><span class="o"><</span><span class="nc">Integer</span><span class="o">>(</span><span class="n">rt</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span> <span class="o">;</span> <span class="n">i</span><span class="o">++)</span>
<span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getName</span><span class="o">()</span>
<span class="o">+</span> <span class="s">" 的循环变量i的值:"</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">20</span><span class="o">)</span>
<span class="o">{</span>
<span class="c1">// 实质还是以Callable对象来创建、并启动线程</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(</span><span class="n">task</span> <span class="o">,</span> <span class="s">"有返回值的线程"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">try</span>
<span class="o">{</span>
<span class="c1">// 获取线程返回值</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"子线程的返回值:"</span> <span class="o">+</span> <span class="n">task</span><span class="o">.</span><span class="na">get</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">ex</span><span class="o">)</span>
<span class="o">{</span>
<span class="n">ex</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p>三种方式对比
通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定 义的方法有返回值,可以声明抛出异常而已。因此可以将实现Runnable接口和实现Callable接口归为一种方式。这种方式与继承Thread方式之间的主要差别如下。</p>
<ul>
<li>采用实现Runnable、Callable接 口的方式创建多线程的优缺点:
<ul>
<li>线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。</li>
<li>在这种方式下,多个线程可以共享同一-个target对象,所以非常适合多个相同线程来处理同一-份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。</li>
<li>劣势是,编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。</li>
</ul>
</li>
<li>采用继承Thread类的方式创建多线程的优缺点:
<ul>
<li>劣势是,因为线程类已经继承了Thread类,所以不能再继承其他父类。</li>
<li>优势是,编写简单,如果需要访问当前线程则无须使用Thread.currentThread()方法,直接使用this即可获得当前线程。鉴于上面分析,因此一般推荐采用实现Runnable接口、Callable接 口的方式来创建多线程。</li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="线程的生命周期">线程的生命周期</h4>
<p><img src="/resources/images/2020-06-05/2020-06-05-18-21-30.png" alt="" /></p>
<p>如上图所示为线程的生命周期,和操作系统中介绍的进程状态差不多。不过要注意,线程死亡状态后不能重新start,必须要重新new到新建状态才能执行。</p>
<h4 id="控制线程">控制线程</h4>
<ul>
<li>join线程:使用th.join()这样的方法可以等待th线程结束后再继续执行下面的代码。</li>
<li>守护线程:使用th.setDaemon(true)可以设置当前线程为守护线程,当前台线程全部死亡后守护线程自动结束。</li>
<li>sleep线程睡眠:使用Thread.sleep(1000)这样的形式可以使得当前线程阻塞一定时间,传入数值为毫秒。</li>
<li>yield线程让步:Thread.yield()将处理器资源让出来给优先级相等或者更高的线程。可以用作协程。</li>
<li>改变线程优先级:Java线程优先级是1-10(不同操作系统可能会有一点差异)。可以通过Thread.currentThread().setPriority(6)进行设置。同时也有Thread.MAX_PRIORITY、Thread.MIN_PRIORITY、Thread.NORM_PRIORITY三个等级可以进行设置。</li>
</ul>
<h4 id="线程同步">线程同步</h4>
<p>当两个线程对同一个对象进行操作时有可能引起数据异常问题,为了解决不同线程对同一个对象的正确操作,需要进行线程同步,Java中有两个解决方案:</p>
<ol>
<li>
<p>使用synchronized关键字修饰方法或者代码块</p>
<p>例如:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">drawAll</span><span class="o">(){</span>
<span class="k">try</span><span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">flag</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"线程:"</span><span class="o">+</span><span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getName</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"取钱:"</span><span class="o">+</span><span class="n">balance</span><span class="o">);</span>
<span class="n">balance</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span>
<span class="n">flag</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">notifyAll</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">else</span><span class="o">{</span>
<span class="n">wait</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div> </div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">synchronized</span> <span class="o">(</span><span class="n">account</span><span class="o">)</span>
<span class="o">{</span>
<span class="c1">// 账户余额大于取钱数目</span>
<span class="k">if</span> <span class="o">(</span><span class="n">account</span><span class="o">.</span><span class="na">getBalance</span><span class="o">()</span> <span class="o">>=</span> <span class="n">drawAmount</span><span class="o">)</span>
<span class="o">{</span>
<span class="c1">// 吐出钞票</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">getName</span><span class="o">()</span>
<span class="o">+</span> <span class="s">"取钱成功!吐出钞票:"</span> <span class="o">+</span> <span class="n">drawAmount</span><span class="o">);</span>
<span class="k">try</span>
<span class="o">{</span>
<span class="nc">Thread</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">ex</span><span class="o">)</span>
<span class="o">{</span>
<span class="n">ex</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// 修改余额</span>
<span class="n">account</span><span class="o">.</span><span class="na">setBalance</span><span class="o">(</span><span class="n">account</span><span class="o">.</span><span class="na">getBalance</span><span class="o">()</span> <span class="o">-</span> <span class="n">drawAmount</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"\t余额为: "</span> <span class="o">+</span> <span class="n">account</span><span class="o">.</span><span class="na">getBalance</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">else</span>
<span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">"取钱失败!余额不足!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p>使用同步锁</p>
<p>Java提供了多种锁的实现,线程安全控制中,使用较多时是可重入锁。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Account</span><span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ReentrantLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ReentrantLock</span><span class="o">();</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">accountNo</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">double</span> <span class="n">balance</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">Account</span><span class="o">(</span><span class="nc">String</span> <span class="n">accountNo</span><span class="o">,</span> <span class="kt">double</span> <span class="n">balance</span><span class="o">){</span>
<span class="k">this</span><span class="o">.</span><span class="na">accountNo</span> <span class="o">=</span> <span class="n">accountNo</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">balance</span> <span class="o">=</span> <span class="n">balance</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">draw</span><span class="o">(</span><span class="kt">double</span> <span class="n">drawAmount</span><span class="o">){</span>
<span class="n">lock</span><span class="o">.</span><span class="na">lock</span><span class="o">();</span>
<span class="k">try</span><span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">balance</span> <span class="o">>=</span> <span class="n">drawAmount</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"取钱:"</span> <span class="o">+</span> <span class="n">drawAmount</span><span class="o">);</span>
<span class="n">balance</span> <span class="o">-=</span> <span class="n">drawAmount</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"余额:"</span> <span class="o">+</span> <span class="n">balance</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">else</span><span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"余额不足!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">finally</span> <span class="o">{</span>
<span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestLock</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Account</span> <span class="n">a1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Account</span><span class="o">(</span><span class="s">"Java"</span><span class="o">,</span> <span class="mi">100</span><span class="o">);</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(()-></span><span class="n">a1</span><span class="o">.</span><span class="na">draw</span><span class="o">(</span><span class="mi">100</span><span class="o">)).</span><span class="na">start</span><span class="o">();</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(()-></span><span class="n">a1</span><span class="o">.</span><span class="na">draw</span><span class="o">(</span><span class="mi">100</span><span class="o">)).</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
</ol>
<h4 id="线程通信">线程通信</h4>
<p>为了保证线程协调运行,Java中提供了一些用于控制线程之间通知对方状况的方法。</p>
<ol>
<li>
<p>传统synchronized修饰的方法</p>
<p>对于这种隐式同步方法,Java提供了三个函数可直接使用。</p>
<ul>
<li>wait():当前线程进行等待,直到其他线程执行notify()或者notifyAll()或者wait中传入的时间已到则继续抢占cpu运行。</li>
<li>notify():随机通知一个线程进行运行。</li>
<li>notifyAll():通知所有阻塞线程运行。</li>
</ul>
<p>例如:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Account2</span><span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ReentrantLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ReentrantLock</span><span class="o">();</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">accountNo</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">double</span> <span class="n">balance</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">flag</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">Account2</span><span class="o">(</span><span class="nc">String</span> <span class="n">accountNo</span><span class="o">,</span> <span class="kt">double</span> <span class="n">balance</span><span class="o">){</span>
<span class="k">this</span><span class="o">.</span><span class="na">accountNo</span> <span class="o">=</span> <span class="n">accountNo</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">balance</span> <span class="o">=</span> <span class="n">balance</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">drawAll</span><span class="o">(){</span>
<span class="k">try</span><span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">flag</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"线程:"</span><span class="o">+</span><span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getName</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"取钱:"</span><span class="o">+</span><span class="n">balance</span><span class="o">);</span>
<span class="n">balance</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span>
<span class="n">flag</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">notifyAll</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">else</span><span class="o">{</span>
<span class="n">wait</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">deposit</span><span class="o">(</span><span class="kt">double</span> <span class="n">depositAmount</span><span class="o">){</span>
<span class="k">try</span><span class="o">{</span>
<span class="k">if</span><span class="o">(!</span><span class="n">flag</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"线程:"</span><span class="o">+</span><span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getName</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"存钱:"</span><span class="o">+</span><span class="n">depositAmount</span><span class="o">);</span>
<span class="n">balance</span><span class="o">+=</span><span class="n">depositAmount</span><span class="o">;</span>
<span class="n">flag</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="n">notifyAll</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">else</span><span class="o">{</span>
<span class="n">wait</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestCommunication</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Account2</span> <span class="n">a2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Account2</span><span class="o">(</span><span class="s">"Java"</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(()->{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">10</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">a2</span><span class="o">.</span><span class="na">drawAll</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">},</span> <span class="s">"取钱线程"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(()->{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">100</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">105</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">a2</span><span class="o">.</span><span class="na">deposit</span><span class="o">(</span><span class="n">i</span><span class="o">*</span><span class="mf">0.1</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">},</span> <span class="s">"存钱线程1"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(()->{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">10000</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">10005</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">a2</span><span class="o">.</span><span class="na">deposit</span><span class="o">(</span><span class="n">i</span><span class="o">*</span><span class="mf">0.1</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">},</span> <span class="s">"存钱线程2"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(()->{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">20000</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">20005</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">a2</span><span class="o">.</span><span class="na">deposit</span><span class="o">(</span><span class="n">i</span><span class="o">*</span><span class="mf">0.1</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">},</span> <span class="s">"存钱线程3"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p>Lock锁实现的方法</p>
<p>由于是显式的锁,因此上面提到的三种方法不可用,但是仍然有相似的三种方法:await()、signal()、signalAll()</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 显式定义Lock对象</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Lock</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ReentrantLock</span><span class="o">();</span>
<span class="c1">// 获得指定Lock对象对应的Condition</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Condition</span> <span class="n">cond</span> <span class="o">=</span> <span class="n">lock</span><span class="o">.</span><span class="na">newCondition</span><span class="o">();</span>
<span class="n">cond</span><span class="o">.</span><span class="na">wait</span><span class="o">();</span>
<span class="n">cond</span><span class="o">.</span><span class="na">signal</span><span class="o">();</span>
<span class="n">cond</span><span class="o">.</span><span class="na">signalAll</span><span class="o">();</span>
</code></pre></div> </div>
</li>
<li>
<p>使用阻塞队列</p>
<p>Java 5提供了一个BlockingQueue接口,虽然BlockingQueue也是Queue的子接口,但它的主要用途并不是作为容器,而是作为线程同步的工具。BlockingQueue 具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,即可很好地控制线程的通信。BlockingQueue提供如下两个支持阻塞的方法。</p>
<ul>
<li>
<p>put(E e):尝试把E元素放入BlockingQueue中,如果该队列的元素已满,则阻塞该线程。</p>
</li>
<li>
<p>take(): 尝试从BlockingQueue的头部取出元素,如果该队列的元素已空,则阻塞该线程。</p>
</li>
</ul>
<p>例如:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Producer</span> <span class="kd">extends</span> <span class="nc">Thread</span><span class="o">{</span>
<span class="kd">private</span> <span class="nc">BlockingQueue</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">bq</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">Producer</span><span class="o">(</span><span class="nc">BlockingQueue</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">bq</span><span class="o">){</span>
<span class="k">this</span><span class="o">.</span><span class="na">bq</span> <span class="o">=</span> <span class="n">bq</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">(){</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">bq</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">" 生产:"</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">Customer</span> <span class="kd">extends</span> <span class="nc">Thread</span><span class="o">{</span>
<span class="kd">private</span> <span class="nc">BlockingQueue</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">bq</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">Customer</span><span class="o">(</span><span class="nc">BlockingQueue</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">bq</span><span class="o">){</span>
<span class="k">this</span><span class="o">.</span><span class="na">bq</span> <span class="o">=</span> <span class="n">bq</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">(){</span>
<span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">){</span>
<span class="k">try</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">v</span> <span class="o">=</span> <span class="n">bq</span><span class="o">.</span><span class="na">take</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"消费:"</span> <span class="o">+</span> <span class="n">v</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestBlockingQueue</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">BlockingQueue</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">bq</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayBlockingQueue</span><span class="o"><>(</span><span class="mi">2</span><span class="o">);</span>
<span class="k">new</span> <span class="nf">Producer</span><span class="o">(</span><span class="n">bq</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="k">new</span> <span class="nf">Producer</span><span class="o">(</span><span class="n">bq</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="k">new</span> <span class="nf">Customer</span><span class="o">(</span><span class="n">bq</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
</ol>
<h4 id="线程池">线程池</h4>
<p>系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互。在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。</p>
<p>与数据库连接池类似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个空闲的线程来执行它们的run)或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。</p>
<p>下面是使用线程池的一个例子:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestThreadPool</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ExecutorService</span> <span class="n">pool</span> <span class="o">=</span> <span class="nc">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">6</span><span class="o">);</span>
<span class="nc">Runnable</span> <span class="n">target</span> <span class="o">=</span> <span class="o">()->{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">" 的值:"</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="n">pool</span><span class="o">.</span><span class="na">submit</span><span class="o">(</span><span class="n">target</span><span class="o">);</span>
<span class="n">pool</span><span class="o">.</span><span class="na">submit</span><span class="o">(</span><span class="n">target</span><span class="o">);</span>
<span class="n">pool</span><span class="o">.</span><span class="na">shutdown</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="网络编程">网络编程</h3>
<h4 id="java基本网络支持">Java基本网络支持</h4>
<p>Java的java.net包下面提供了一些网络操作的基本包,可以通过调用他们的方法进行一些基本操作,如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestInetAddress</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">InetAddress</span> <span class="n">ip</span> <span class="o">=</span> <span class="nc">InetAddress</span><span class="o">.</span><span class="na">getByName</span><span class="o">(</span><span class="s">"blog.woodcoding.com"</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">ip</span><span class="o">.</span><span class="na">isReachable</span><span class="o">(</span><span class="mi">1000</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">ip</span><span class="o">.</span><span class="na">getHostAddress</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">ip</span><span class="o">.</span><span class="na">getHostName</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">ip</span><span class="o">.</span><span class="na">getCanonicalHostName</span><span class="o">());</span>
<span class="nc">InetAddress</span> <span class="n">local</span> <span class="o">=</span> <span class="nc">InetAddress</span><span class="o">.</span><span class="na">getByAddress</span><span class="o">(</span><span class="k">new</span> <span class="kt">byte</span><span class="o">[]{(</span><span class="kt">byte</span><span class="o">)</span> <span class="mi">185</span><span class="o">,</span> <span class="o">(</span><span class="kt">byte</span><span class="o">)</span> <span class="mi">199</span><span class="o">,</span><span class="mi">109</span><span class="o">,</span> <span class="o">(</span><span class="kt">byte</span><span class="o">)</span> <span class="mi">153</span><span class="o">});</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">local</span><span class="o">.</span><span class="na">isReachable</span><span class="o">(</span><span class="mi">1000</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">local</span><span class="o">.</span><span class="na">getHostAddress</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">local</span><span class="o">.</span><span class="na">getHostName</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">local</span><span class="o">.</span><span class="na">getCanonicalHostName</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="tcp通信">TCP通信</h4>
<ol>
<li>
<p>TCP基本通信
TCP是可靠连接,通过socket可以进行tcp通信,如下是一个客户端和服务端通信的例子:</p>
<p>server:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Server</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">ServerSocket</span> <span class="n">ss</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ServerSocket</span><span class="o">(</span><span class="mi">6666</span><span class="o">);</span>
<span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">){</span>
<span class="nc">Socket</span> <span class="n">s</span> <span class="o">=</span> <span class="n">ss</span><span class="o">.</span><span class="na">accept</span><span class="o">();</span>
<span class="nc">PrintStream</span> <span class="n">ps</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PrintStream</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">getOutputStream</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"服务器连接成功!"</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"欢迎使用!"</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="n">s</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
<p>client:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Client</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">host</span> <span class="o">=</span> <span class="s">"127.0.0.1"</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">port</span> <span class="o">=</span> <span class="mi">6666</span><span class="o">;</span>
<span class="nc">Socket</span> <span class="n">socket</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Socket</span><span class="o">();</span>
<span class="n">socket</span><span class="o">.</span><span class="na">connect</span><span class="o">(</span><span class="k">new</span> <span class="nc">InetSocketAddress</span><span class="o">(</span><span class="n">host</span><span class="o">,</span> <span class="n">port</span><span class="o">),</span> <span class="mi">10000</span><span class="o">);</span>
<span class="n">socket</span><span class="o">.</span><span class="na">setSoTimeout</span><span class="o">(</span><span class="mi">3000</span><span class="o">);</span>
<span class="k">try</span><span class="o">{</span>
<span class="nc">Scanner</span> <span class="n">scan</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Scanner</span><span class="o">(</span><span class="n">socket</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">());</span>
<span class="k">while</span> <span class="o">(</span><span class="n">scan</span><span class="o">.</span><span class="na">hasNext</span><span class="o">()){</span>
<span class="nc">String</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">msg</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">scan</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="n">socket</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="nc">SocketTimeoutException</span> <span class="n">ex</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"超时!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p>TCP多线程通信
下面是一个多线程、多客户端通信的例子:</p>
<p>server:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ServerThread</span> <span class="kd">implements</span> <span class="nc">Runnable</span><span class="o">{</span>
<span class="nc">Socket</span> <span class="n">s</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">ServerThread</span><span class="o">(</span><span class="nc">Socket</span> <span class="n">s</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">getPort</span><span class="o">()+</span><span class="s">" 端口客户端已启动!"</span><span class="o">);</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">;</span>
<span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">()));</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">msg</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">while</span> <span class="o">((</span><span class="n">msg</span> <span class="o">=</span> <span class="n">readMsg</span><span class="o">())!=</span><span class="kc">null</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"来自 "</span> <span class="o">+</span> <span class="n">s</span><span class="o">.</span><span class="na">getPort</span><span class="o">()</span> <span class="o">+</span> <span class="s">" 的消息:"</span> <span class="o">+</span> <span class="n">msg</span><span class="o">);</span>
<span class="k">for</span><span class="o">(</span><span class="nc">Socket</span> <span class="nl">client:</span> <span class="nc">MyServer</span><span class="o">.</span><span class="na">socketList</span><span class="o">){</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">PrintStream</span> <span class="n">ps</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PrintStream</span><span class="o">(</span><span class="n">client</span><span class="o">.</span><span class="na">getOutputStream</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">client</span><span class="o">.</span><span class="na">getPort</span><span class="o">()</span> <span class="o">+</span> <span class="s">": "</span> <span class="o">+</span> <span class="n">msg</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="nf">readMsg</span><span class="o">(){</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">MyServer</span><span class="o">.</span><span class="na">socketList</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">getPort</span><span class="o">()</span> <span class="o">+</span> <span class="s">"已退出!"</span><span class="o">);</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyServer</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Socket</span><span class="o">></span> <span class="n">socketList</span> <span class="o">=</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">synchronizedList</span><span class="o">(</span><span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>());</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">ServerSocket</span> <span class="n">ss</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ServerSocket</span><span class="o">(</span><span class="mi">6666</span><span class="o">);</span>
<span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">){</span>
<span class="nc">Socket</span> <span class="n">s</span> <span class="o">=</span> <span class="n">ss</span><span class="o">.</span><span class="na">accept</span><span class="o">();</span>
<span class="n">socketList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(</span><span class="k">new</span> <span class="nc">ServerThread</span><span class="o">(</span><span class="n">s</span><span class="o">)).</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
<p>client:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ClientThread</span> <span class="kd">implements</span> <span class="nc">Runnable</span><span class="o">{</span>
<span class="kd">private</span> <span class="nc">Socket</span> <span class="n">s</span><span class="o">;</span>
<span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">ClientThread</span><span class="o">(</span><span class="nc">Socket</span> <span class="n">s</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">;</span>
<span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">()));</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">msg</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">while</span> <span class="o">((</span><span class="n">msg</span> <span class="o">=</span> <span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">())!=</span><span class="kc">null</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">msg</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyClient</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">Socket</span> <span class="n">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Socket</span><span class="o">(</span><span class="s">"127.0.0.1"</span><span class="o">,</span> <span class="mi">6666</span><span class="o">);</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClientThread</span><span class="o">(</span><span class="n">server</span><span class="o">)).</span><span class="na">start</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">msg</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="nc">PrintStream</span> <span class="n">ps</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PrintStream</span><span class="o">(</span><span class="n">server</span><span class="o">.</span><span class="na">getOutputStream</span><span class="o">());</span>
<span class="c1">// Scanner input = new Scanner(System.in);</span>
<span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">((</span><span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">)));</span>
<span class="k">while</span> <span class="o">((</span><span class="n">msg</span> <span class="o">=</span> <span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">())</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">){</span>
<span class="c1">// msg = input.nextLine();</span>
<span class="n">ps</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">msg</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
</ol>
<h4 id="udp通信">UDP通信</h4>
<p>UDP是不可靠的连接,尽最大努力交付,在电话、视频、直播、游戏等方面用的比较多,也可以实现消息广播。如下是一个UDP实现通信的例子:</p>
<p>server:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UdpServer</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">PORT</span> <span class="o">=</span> <span class="mi">3000</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">DATA_LEN</span> <span class="o">=</span> <span class="mi">4096</span><span class="o">;</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">inBuff</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="no">DATA_LEN</span><span class="o">];</span>
<span class="kd">private</span> <span class="nc">DatagramPacket</span> <span class="n">inPack</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatagramPacket</span><span class="o">(</span><span class="n">inBuff</span><span class="o">,</span> <span class="n">inBuff</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
<span class="kd">private</span> <span class="nc">DatagramPacket</span> <span class="n">outPack</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="nc">String</span><span class="o">[]</span> <span class="n">books</span> <span class="o">=</span> <span class="k">new</span> <span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">String</span><span class="o">[]{</span>
<span class="s">"Java"</span><span class="o">,</span>
<span class="s">"Python"</span><span class="o">,</span>
<span class="s">"C++"</span><span class="o">,</span>
<span class="o">};</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">DatagramSocket</span> <span class="n">socket</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatagramSocket</span><span class="o">(</span><span class="no">PORT</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">socket</span><span class="o">.</span><span class="na">receive</span><span class="o">(</span><span class="n">inPack</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="n">inBuff</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">inBuff</span><span class="o">.</span><span class="na">length</span><span class="o">));</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">sendData</span> <span class="o">=</span> <span class="n">books</span><span class="o">[</span><span class="n">i</span><span class="o">%</span><span class="mi">3</span><span class="o">].</span><span class="na">getBytes</span><span class="o">();</span>
<span class="n">outPack</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatagramPacket</span><span class="o">(</span><span class="n">sendData</span><span class="o">,</span> <span class="n">sendData</span><span class="o">.</span><span class="na">length</span><span class="o">,</span> <span class="n">inPack</span><span class="o">.</span><span class="na">getSocketAddress</span><span class="o">());</span>
<span class="n">socket</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="n">outPack</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="k">new</span> <span class="nf">UdpServer</span><span class="o">().</span><span class="na">init</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>client:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UdpClient</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">DEST_IP</span> <span class="o">=</span> <span class="s">"127.0.0.1"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">DEST_PORT</span> <span class="o">=</span> <span class="mi">3000</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">DATE_LEN</span> <span class="o">=</span> <span class="mi">4096</span><span class="o">;</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">inBuff</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="no">DATE_LEN</span><span class="o">];</span>
<span class="kd">private</span> <span class="nc">DatagramPacket</span> <span class="n">inPack</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatagramPacket</span><span class="o">(</span><span class="n">inBuff</span><span class="o">,</span> <span class="n">inBuff</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
<span class="kd">private</span> <span class="nc">DatagramPacket</span> <span class="n">outPack</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">DatagramSocket</span> <span class="n">socket</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatagramSocket</span><span class="o">();</span>
<span class="n">outPack</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatagramPacket</span><span class="o">(</span><span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="mi">0</span><span class="o">],</span> <span class="mi">0</span><span class="o">,</span> <span class="nc">InetAddress</span><span class="o">.</span><span class="na">getByName</span><span class="o">(</span><span class="no">DEST_IP</span><span class="o">),</span> <span class="no">DEST_PORT</span><span class="o">);</span>
<span class="nc">Scanner</span> <span class="n">scan</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Scanner</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">);</span>
<span class="k">while</span> <span class="o">(</span><span class="n">scan</span><span class="o">.</span><span class="na">hasNext</span><span class="o">()){</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">buff</span> <span class="o">=</span> <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">().</span><span class="na">getBytes</span><span class="o">();</span>
<span class="n">outPack</span><span class="o">.</span><span class="na">setData</span><span class="o">(</span><span class="n">buff</span><span class="o">);</span>
<span class="n">socket</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="n">outPack</span><span class="o">);</span>
<span class="n">socket</span><span class="o">.</span><span class="na">receive</span><span class="o">(</span><span class="n">inPack</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="n">inBuff</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">inPack</span><span class="o">.</span><span class="na">getLength</span><span class="o">()));</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="k">new</span> <span class="nf">UdpClient</span><span class="o">().</span><span class="na">init</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>参考资料:</p>
<ol>
<li>疯狂Java讲义(第三版)-李刚</li>
<li>https://www.cnblogs.com/chenglc/p/6922834.html</li>
<li>https://www.cnblogs.com/wanlipeng/archive/2010/10/21/1857791.html</li>
<li>https://www.cnblogs.com/guanghuiqq/p/11208809.html</li>
</ol>woodcoding JAVA基础系列,包括: 数据类型 流程控制与数组 面向对象 基础类库 异常 注解 反射 输入输出流 多线程 网络编程有限状态机(FSM)2020-05-23T20:30:00+00:002020-05-23T20:30:00+00:00http://blog.woodcoding.com/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/2020/05/23/python-transitions<h3 id="摘要">摘要</h3>
<p> 由于前几个月帮同学写了两个项目,其中涉及到了工作流的设计。比如,一个任务在发生什么操作之后应该要转到哪个用户进行下一步操作。如果只是单纯的更改数据库status字段肯定是不行的,耦合度太高,后续不容易维护,因此想到使用工作流(workflow)引擎。搜索资料发现java中是有activiti这样强大的引擎,但是目前Python中好像并没有发现比较好的。所以最后就找到这个Transitions库,比较简单,而工作流抽象出原理其实就是大学编译原理所学的状态机,因此我们用它来实现相关操作。</p>
<!-- more -->
<h3 id="问题分析">问题分析</h3>
<p>假设我们有一个审批流程,现在由A发起,同时需要B、C进行审批,B、C审批完成之后由D审批结束。如果D不同意则驳回A重新提交。流程图如下:</p>
<pre><code class="language-mermaid">graph LR;
id1((开始))
id1 --> A((A))
A -- 提交 --> B((B))
A -- 提交 --> C((C))
B -- 审批 --> D((D))
C -- 审批 --> D((D))
D --- id2((结束))
D -- 拒绝 --> A
</code></pre>
<p>像这种状态,我们肯定是可以通过修改数据库字段来进行的,但是不便于维护,那么我们该如何规范化实现呢?</p>
<h3 id="工作流介绍">工作流介绍</h3>
<h4 id="顺序工作流">顺序工作流</h4>
<p>顺序工作流的执行过程是一个连续的步骤序列,它在完成一个活动之后会自动去执行到下一个。这种工作流就类似我们提交某些操作时需要进行系列步骤一样。</p>
<h4 id="状态机工作流">状态机工作流</h4>
<p>事件驱动工作流则依赖外部事件来驱动执行直到完成,事件驱动工作流也叫做状态机(state machine)工作流。状态机中包含一系列状态(包括初始状态和结束状态)和事件.状态机总是停在一个预设的状态中,直到事件触发之后才会跳转到新的状态上.状态机工作流这样做的好处就是它可以定义状态,定义工作流如何从一个状态到另外一个状态。下面介绍状态机。</p>
<h3 id="状态机">状态机</h3>
<p>有限状态机(Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。FSM是一种算法思想,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。现实世界中存在大量具有有限个状态的系统:钟表系统、电梯系统、交通信号灯系统、通信协议系统、正则表达式、硬件电路系统设计、软件工程,编译器等,有限状态机的概念就是来自于现实世界中的这些有限系统。</p>
<p>有限状态机是一个五元组M=(Q,Σ,δ,q0,F),其中:</p>
<ul>
<li>Q={q0,q1,…,qn}是有限状态集合。在任一确定的时刻,有限状态机只能处于一个确定的状态qi;</li>
<li>Σ={σ1,σ2,…,σn}是有限输入字符集合。在任一确定的时刻,有限状态机只能接收一个确定的输入σj;</li>
<li>δ:Q×Σ→Q是状态转移函数,在某一状态下,给定输入后有限状态机将转入状态迁移函数决定的一个新状态;</li>
<li>q0∈Q是初始状态,有限状态机由此状态开始接收输入;</li>
<li>F⊆Q是最终状态集合,有限状态机在达到终态后不再接收输入。</li>
</ul>
<h3 id="状态机实现">状态机实现</h3>
<ol>
<li>
<p>选择结构实现</p>
<p>就是使用switch-case或if-else进行实现,这样当状态转移比较少的时候确实可以方便实现,但是代码可读性并不好,并且如果状态过多时状态机将难以扩展和维护。</p>
</li>
<li>
<p>状态表实现</p>
<p>维护一个二维状态表,横坐标表示当前状态,纵坐标表示输入,表中一个元素存储下一个状态和对应的操作。这一招易于维护,但是运行时间和存储空间的代价较大。</p>
</li>
<li>
<p>面向对象实现</p>
<p>这里直接放大神的代码给大家参考。
statemachine.py</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">StateMachine</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">handlers</span> <span class="o">=</span> <span class="p">{}</span> <span class="c1"># 状态转移函数字典
</span> <span class="bp">self</span><span class="p">.</span><span class="n">startState</span> <span class="o">=</span> <span class="bp">None</span> <span class="c1"># 初始状态
</span> <span class="bp">self</span><span class="p">.</span><span class="n">endStates</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1"># 最终状态集合
</span>
<span class="c1"># 参数name为状态名,handler为状态转移函数,end_state表明是否为最终状态
</span> <span class="k">def</span> <span class="nf">add_state</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">handler</span><span class="p">,</span> <span class="n">end_state</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">name</span><span class="p">.</span><span class="n">upper</span><span class="p">()</span> <span class="c1"># 转换为大写
</span> <span class="bp">self</span><span class="p">.</span><span class="n">handlers</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">handler</span>
<span class="k">if</span> <span class="n">end_state</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">endStates</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">set_start</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">startState</span> <span class="o">=</span> <span class="n">name</span><span class="p">.</span><span class="n">upper</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cargo</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">handler</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">handlers</span><span class="p">[</span><span class="bp">self</span><span class="p">.</span><span class="n">startState</span><span class="p">]</span>
<span class="k">except</span><span class="p">:</span>
<span class="k">raise</span> <span class="nb">ValueError</span><span class="p">(</span><span class="s">"must call .set_start() before .run()"</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">endStates</span><span class="p">:</span>
<span class="k">raise</span> <span class="nb">ValueError</span><span class="p">(</span><span class="s">"at least one state must be an end_state"</span><span class="p">)</span>
<span class="c1"># 从Start状态开始进行处理
</span> <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="p">(</span><span class="n">newState</span><span class="p">,</span> <span class="n">cargo</span><span class="p">)</span> <span class="o">=</span> <span class="n">handler</span><span class="p">(</span><span class="n">cargo</span><span class="p">)</span> <span class="c1"># 经过状态转移函数变换到新状态
</span> <span class="k">if</span> <span class="n">newState</span><span class="p">.</span><span class="n">upper</span><span class="p">()</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">endStates</span><span class="p">:</span> <span class="c1"># 如果跳到终止状态,则打印状态并结束循环
</span> <span class="k">print</span><span class="p">(</span><span class="s">"reached "</span><span class="p">,</span> <span class="n">newState</span><span class="p">)</span>
<span class="k">break</span>
<span class="k">else</span><span class="p">:</span> <span class="c1"># 否则将转移函数切换为新状态下的转移函数
</span> <span class="n">handler</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">handlers</span><span class="p">[</span><span class="n">newState</span><span class="p">.</span><span class="n">upper</span><span class="p">()]</span>
</code></pre></div> </div>
<p>run.py</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">statemachine</span> <span class="kn">import</span> <span class="n">StateMachine</span>
<span class="c1"># 有限状态集合
</span> <span class="n">positive_adjectives</span> <span class="o">=</span> <span class="p">[</span><span class="s">"great"</span><span class="p">,</span> <span class="s">"super"</span><span class="p">,</span> <span class="s">"fun"</span><span class="p">,</span> <span class="s">"entertaining"</span><span class="p">,</span> <span class="s">"easy"</span><span class="p">]</span>
<span class="n">negative_adjectives</span> <span class="o">=</span> <span class="p">[</span><span class="s">"boring"</span><span class="p">,</span> <span class="s">"difficult"</span><span class="p">,</span> <span class="s">"ugly"</span><span class="p">,</span> <span class="s">"bad"</span><span class="p">]</span>
<span class="c1"># 自定义状态转变函数
</span> <span class="k">def</span> <span class="nf">start_transitions</span><span class="p">(</span><span class="n">txt</span><span class="p">):</span>
<span class="c1"># 过指定分隔符对字符串进行切片,默认为空格分割,参数num指定分割次数
</span> <span class="c1"># 将"Python is XXX"语句分割为"Python"和之后的"is XXX"
</span> <span class="n">splitted_txt</span> <span class="o">=</span> <span class="n">txt</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">word</span><span class="p">,</span> <span class="n">txt</span> <span class="o">=</span> <span class="n">splitted_txt</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">splitted_txt</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span> <span class="k">else</span> <span class="p">(</span><span class="n">txt</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
<span class="k">if</span> <span class="n">word</span> <span class="o">==</span> <span class="s">"Python"</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"Python_state"</span> <span class="c1"># 如果第一个词是Python则可转换到"Python状态"
</span> <span class="k">else</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"error_state"</span> <span class="c1"># 如果第一个词不是Python则进入终止状态
</span> <span class="k">return</span> <span class="p">(</span><span class="n">newState</span><span class="p">,</span> <span class="n">txt</span><span class="p">)</span> <span class="c1"># 返回新状态和余下的语句txt
</span>
<span class="k">def</span> <span class="nf">python_state_transitions</span><span class="p">(</span><span class="n">txt</span><span class="p">):</span>
<span class="n">splitted_txt</span> <span class="o">=</span> <span class="n">txt</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">word</span><span class="p">,</span> <span class="n">txt</span> <span class="o">=</span> <span class="n">splitted_txt</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">splitted_txt</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span> <span class="k">else</span> <span class="p">(</span><span class="n">txt</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
<span class="k">if</span> <span class="n">word</span> <span class="o">==</span> <span class="s">"is"</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"is_state"</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"error_state"</span>
<span class="k">return</span> <span class="p">(</span><span class="n">newState</span><span class="p">,</span> <span class="n">txt</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">is_state_transitions</span><span class="p">(</span><span class="n">txt</span><span class="p">):</span>
<span class="n">splitted_txt</span> <span class="o">=</span> <span class="n">txt</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">word</span><span class="p">,</span> <span class="n">txt</span> <span class="o">=</span> <span class="n">splitted_txt</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">splitted_txt</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span> <span class="k">else</span> <span class="p">(</span><span class="n">txt</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
<span class="k">if</span> <span class="n">word</span> <span class="o">==</span> <span class="s">"not"</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"not_state"</span>
<span class="k">elif</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">positive_adjectives</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"pos_state"</span>
<span class="k">elif</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">negative_adjectives</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"neg_state"</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"error_state"</span>
<span class="k">return</span> <span class="p">(</span><span class="n">newState</span><span class="p">,</span> <span class="n">txt</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">not_state_transitions</span><span class="p">(</span><span class="n">txt</span><span class="p">):</span>
<span class="n">splitted_txt</span> <span class="o">=</span> <span class="n">txt</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">word</span><span class="p">,</span> <span class="n">txt</span> <span class="o">=</span> <span class="n">splitted_txt</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">splitted_txt</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span> <span class="k">else</span> <span class="p">(</span><span class="n">txt</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
<span class="k">if</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">positive_adjectives</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"neg_state"</span>
<span class="k">elif</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">negative_adjectives</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"pos_state"</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">newState</span> <span class="o">=</span> <span class="s">"error_state"</span>
<span class="k">return</span> <span class="p">(</span><span class="n">newState</span><span class="p">,</span> <span class="n">txt</span><span class="p">)</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">m</span> <span class="o">=</span> <span class="n">StateMachine</span><span class="p">()</span>
<span class="n">m</span><span class="p">.</span><span class="n">add_state</span><span class="p">(</span><span class="s">"Start"</span><span class="p">,</span> <span class="n">start_transitions</span><span class="p">)</span> <span class="c1"># 添加初始状态
</span> <span class="n">m</span><span class="p">.</span><span class="n">add_state</span><span class="p">(</span><span class="s">"Python_state"</span><span class="p">,</span> <span class="n">python_state_transitions</span><span class="p">)</span>
<span class="n">m</span><span class="p">.</span><span class="n">add_state</span><span class="p">(</span><span class="s">"is_state"</span><span class="p">,</span> <span class="n">is_state_transitions</span><span class="p">)</span>
<span class="n">m</span><span class="p">.</span><span class="n">add_state</span><span class="p">(</span><span class="s">"not_state"</span><span class="p">,</span> <span class="n">not_state_transitions</span><span class="p">)</span>
<span class="n">m</span><span class="p">.</span><span class="n">add_state</span><span class="p">(</span><span class="s">"neg_state"</span><span class="p">,</span> <span class="bp">None</span><span class="p">,</span> <span class="n">end_state</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># 添加最终状态
</span> <span class="n">m</span><span class="p">.</span><span class="n">add_state</span><span class="p">(</span><span class="s">"pos_state"</span><span class="p">,</span> <span class="bp">None</span><span class="p">,</span> <span class="n">end_state</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="n">m</span><span class="p">.</span><span class="n">add_state</span><span class="p">(</span><span class="s">"error_state"</span><span class="p">,</span> <span class="bp">None</span><span class="p">,</span> <span class="n">end_state</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="n">m</span><span class="p">.</span><span class="n">set_start</span><span class="p">(</span><span class="s">"Start"</span><span class="p">)</span> <span class="c1"># 设置开始状态
</span> <span class="n">m</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="s">"Python is great"</span><span class="p">)</span>
<span class="n">m</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="s">"Python is not fun"</span><span class="p">)</span>
<span class="n">m</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="s">"Perl is ugly"</span><span class="p">)</span>
<span class="n">m</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="s">"Pythoniseasy"</span><span class="p">)</span>
</code></pre></div> </div>
</li>
<li>
<p>transitions开源库</p>
</li>
</ol>
<p> transitions是一个由Python实现的轻量级的、面向对象的有限状态机框架。transitions最基本的用法如下,先自定义一个类,然后定义一系列状态和状态转移(定义状态和状态转移有多种方式,下面只写了最简明的一种,具体要参考文档说明),最后初始化状态机。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>```python
from transitions import Machine
# 定义一个自己的类
class Matter(object):
pass
model = Matter()
# 状态定义
states = ['solid', 'liquid', 'gas', 'plasma']
# 定义状态转移
# The trigger argument defines the name of the new triggering method
transitions = [
{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
{'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}]
# 初始化
machine = Machine(model=model, states=states, transitions=transitions, initial='solid')
# Test
print(model.state) # solid
# 状体转变
model.melt()
print(model.state) # liquid
```
</code></pre></div></div>
<p>参考资料:</p>
<ol>
<li>https://www.cnblogs.com/21207-iHome/p/6085334.html</li>
<li>https://blog.csdn.net/u010375663/article/details/43230561</li>
</ol>woodcoding摘要 由于前几个月帮同学写了两个项目,其中涉及到了工作流的设计。比如,一个任务在发生什么操作之后应该要转到哪个用户进行下一步操作。如果只是单纯的更改数据库status字段肯定是不行的,耦合度太高,后续不容易维护,因此想到使用工作流(workflow)引擎。搜索资料发现java中是有activiti这样强大的引擎,但是目前Python中好像并没有发现比较好的。所以最后就找到这个Transitions库,比较简单,而工作流抽象出原理其实就是大学编译原理所学的状态机,因此我们用它来实现相关操作。《数据库系统基础教程》学习笔记2020-03-20T16:00:00+00:002020-03-20T16:00:00+00:00http://blog.woodcoding.com/%E8%AF%BB%E4%B9%A6/2020/03/20/sql-basic-book-study<h2 id="摘要">摘要</h2>
<p> 大学里学的数据库课程也差不多忘了(好像根本没怎么听,就只记得个笛卡尔积和几大范式了哈哈),所以就学习一下。当然,如果对离散数学没有任何认识的话还是建议先看离散数学,因为数据库中很多术语都在离散数学中有定义。</p>
<p>更新记录:</p>
<ul>
<li>2020-03-20: 开始记录</li>
<li>2020-03-26: 完成一、二、三章,把范式搞清楚就好办了</li>
<li>2020-03-27: 完成第四章,E/R图完成,第一部分理论就完成了,准备进入数据库程序设计部分。</li>
<li>2020-04-03: 完成第五章、第六章,基本上重点内容都已经记录到位,后面花个几天把尾巴补上。</li>
<li>2020-04-06: 完成第七章、第八章,对于数据库的基本操作差不多就结束了,后面两个章节都是些数据库的应用了,像我们这种CRUD程序员平时用到的情况也不多,所以可能就挑一些重点内容学习下。</li>
</ul>
<h2 id="待完成">待完成</h2>
<ul class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />第一章 数据库系统世界</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />第二章 关系数据模型</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />第三章 关系数据库设计理论(含范式判断)</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />第四章 高级数据库模型(含E/R图)</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />第五章 代数和逻辑查询语言</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />第六章 数据库语言SQL</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />第七章 约束与触发器</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />第八章 视图与索引</li>
</ul>
<!-- more -->
<h2 id="第一章-数据库系统世界">第一章 数据库系统世界</h2>
<p> 本章节主要是介绍了一些关于数据库的基础概念。</p>
<h3 id="1数据库相关概念">1、数据库相关概念</h3>
<ul>
<li>
<p><strong>数据库是什么?</strong></p>
<p>本质上讲,数据库就是信息的集合。一般来讲,是指由DBMS所管理的数据的集合。</p>
</li>
<li>
<p><strong>关系型数据库与非关系型数据库区别</strong></p>
<p>数据库系统将数据组织成表的形式呈现给用户,这种形式的数据表示称为关系。关系数据库就是采用了关系模型来组织数据的数据库。</p>
</li>
<li>
<p><strong>SQL是什么?</strong></p>
<p>Structured Query Language,结构化查询语言,一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。</p>
</li>
<li>
<p><strong>DB、DBMS、DBA是什么?</strong></p>
<ul>
<li>DB:Database,即数据库</li>
<li>DBMS:Database Management System,即数据库管理系统。我们平时所说的mysql、mssql就是指的DBMS而不是DB</li>
<li>DBA:Database Administrator,即数据库管理员。是从事管理和维护数据库管理系统(DBMS)的相关工作人员的统称。</li>
</ul>
</li>
<li>
<p><strong>数据定义语言(DDL)是什么?</strong></p>
<p>数据库模式定义语言DDL(Data Definition Language),是用于描述数据库中要存储的现实世界实体的语言。</p>
</li>
</ul>
<h3 id="2数据库管理系统">2、数据库管理系统</h3>
<ul>
<li>
<p><strong>DBMS如何执行查询操作?</strong></p>
<p>数据库查询通过查询编译器完成SQL语法的分析与优化,语法送往执行引擎完成一系列小块数据请求,请求送往缓冲区管理器完成从二级存储(通常是磁盘)获取数据送入主存缓冲区。</p>
</li>
<li>
<p><strong>简单了解数据库事务</strong></p>
<p>事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。</p>
</li>
<li>
<p><strong>事务的ACID是什么?</strong></p>
<p>A(atomicity):原子性,事务操作要么全部执行,要么全部不执行<br />
I(isolation):独立性,事务执行时如同没有其他事务同时执行一样。<br />
D(durability):持久性,一旦事务完成,该操作对数据库的影响是永久的。<br />
C(consistency):一致性,事务完成前后数据的完整性必须保持一致。</p>
</li>
</ul>
<h2 id="第二章-关系数据模型">第二章 关系数据模型</h2>
<h3 id="1名词解释">1、名词解释</h3>
<ul>
<li>
<p><strong>数据模型(Data Model)</strong>:数据模型一般用于描述数据库中数据的结构,也包含施加于数
据上的各种约束。通常,数据模型提供了一套规则描述數据上的各种操作,比如数据查
询和数据修改。</p>
</li>
<li>
<p><strong>关系模型(Relational Model)</strong>:关系是表示信息的表。属性位于每列的头部,每个属性
都有相应的域或者数据类型。行被称为元组,每一行都有一个分量与关系属性对应。</p>
</li>
<li>
<p><strong>模式(Schema)</strong>: 关系名和该关系所有属性的结合。多个关系模式形成一个数据库模式。
一个关系或多个关系的特定数据叫做关系模式或数据库模式的实例。</p>
</li>
<li>
<p><strong>键(Key)</strong>:关系上有一类很重要的约束,即由关系的一个属性或者一个属性集组成的
键。没有任何两个元组在组成键的所有属性上具有相同的值,虽然它们有可能在组成键
的部分属性上取值相同。</p>
</li>
<li>
<p><strong>半结构化数据模型(Semistructured Data Model)</strong>:在这种数据模型中,数据以树或者图的形式进行组织。XML是半结构化数据模型中的一个重要实例。</p>
</li>
<li>
<p><strong>SQL</strong>:SQL是关系数据库系统的标准查询语言。最新的标准是SQL-99。目前市面上的商
用数据库并没有完整的实现该标准,而只是实现了其中的一部分。</p>
</li>
<li>
<p><strong>数据定义(Data Definition)</strong>: SQL提供了定义数据库模式中元素的语句,可以利用
CREATE TABLE语句来声明一个存储的关系 (称作表)模式,定义其包含的属性集,各
属性的数据类型、默认值和键等。</p>
</li>
<li>
<p><strong>模式修改(Altering Schema)</strong>:可以利用ALTer语 句来修改数据库的一部分模式。 这些修
改包括在关系模式中增加或者删除属性,改变与某个属性相关联的默认值等。当然,
也可以利用DROP语句将整个关系或者其他模式元素删除。</p>
</li>
<li>
<p><strong>关系代数(RelationalAlgebra)</strong>:代数在关系模型的大多数查询语言中都有所体现。它
的基本操作有并,交、整、选择,投影、笛卡儿积、自然连接、链接及重命名等。</p>
</li>
<li>
<p><strong>选择和投影(Selection and Projection)</strong>:选择操作得到的结果是关系中所有满足选择条
件的元组。投影操作从关系中去掉不感兴趣的列,剩下的输出,形成最终结果。</p>
</li>
<li>
<p><strong>连接(Join)</strong>:通过比较两个关系的每一对元组来进行连接操作。在自然连接当中,把
那些在两个关系的共有属性上值相等的元组接合起来。在θ连接中,则是连接来自两个
关系的一对满足θ连接指定的选择条件的元组。</p>
</li>
<li>
<p><strong>关系代数中的约束(Constraint in Rclational Algcbra)</strong>: 许多常见的约束可以用某个关系
代数表达式被另外一个所包含的形式表达,或者用某个关系代数表达式等于空集的等价
形式表达。</p>
</li>
</ul>
<h3 id="2数据模型">2、数据模型</h3>
<p>数据模型是用于描述数据或信息的标记,一般由三部分组成:</p>
<ul>
<li>数据结构:一种物理数据模型,也称概念模型</li>
<li>数据操作:有限的可执行操作集,如CRUD。</li>
<li>数据约束:描述数据上的关系约束。</li>
</ul>
<p>主流数据模型有:</p>
<ul>
<li>关系模型</li>
<li>半结构化模型</li>
</ul>
<h3 id="3关系模型">3、关系模型</h3>
<p>数据库表Movies</p>
<table>
<thead>
<tr>
<th>title</th>
<th>year</th>
<th>length</th>
<th>genre</th>
</tr>
</thead>
<tbody>
<tr>
<td>Gone with the wind</td>
<td>1939</td>
<td>231</td>
<td>drama</td>
</tr>
<tr>
<td>star wars</td>
<td>1977</td>
<td>124</td>
<td>scifi</td>
</tr>
<tr>
<td>wayne’s world</td>
<td>1992</td>
<td>95</td>
<td>comedy</td>
</tr>
</tbody>
</table>
<p>以上面的关系为例:</p>
<ul>
<li><strong>关系名</strong>:就是表名Movies</li>
<li><strong>属性</strong>:关系的列名如title、year等,由于关系上所有的属性是一个集合而不是列表,因此与排列顺序无关。</li>
<li><strong>模式</strong>:关系名和数据的集合构成的组合。如:Movies(title、year、length、genre)</li>
<li><strong>数据库模式</strong>:多个关系模式组成的集合。</li>
<li><strong>元组</strong>:每一行数组,如:(Gone with the wind, 1939, 231, drama ),由于关系是元组的集合,因此与元组的排列顺序也无关。</li>
<li><strong>域</strong>:元组的每个分量具有原子性,属于某种元素类型,如year属于integer,即为year分量上的域。</li>
<li><strong>实例</strong>:当前关系所有元组的集合称为当前实例。</li>
<li><strong>键</strong>:关系上的任何两个元组的值在键上的取值不同,比如我们平时说的主键id,也可以由多个属性结合成键,如这里的title和year可以组成成键。</li>
</ul>
<h3 id="4代数查询语言">4、代数查询语言</h3>
<h4 id="什么是代数">什么是代数</h4>
<p> 通常一门代数由一些操作符合一些原子操作数组成。如算数代数中的原子操作数是x和15这种,+、-、*、/这些是操作符。任何一门代数都可以把操作符作用在原子操作数上构造表达式。</p>
<h4 id="为何引入代数查询语言">为何引入代数查询语言</h4>
<p> C++/JAVA这些语言已经足够强大,为何还要引入新的语言?答案非常让人惊讶,引入关系代数的原因竟然正是由于关系代数没有C++/JAVA这么强大的特性。在数据库中,我们使用一些简单的代数符号就可以用来表达我们对数据的所有操作。</p>
<h4 id="关系代数">关系代数</h4>
<ul>
<li>操作数:
<ul>
<li>代表关系的变量</li>
<li>代表有限关系的常量</li>
</ul>
</li>
<li>操作符:
<ul>
<li>集合操作<br />
交:$R∩S$<br />
并:$R∪S$<br />
差:$R-S$</li>
<li>
<p>投影($π$)<br />
其实就是过滤了一些属性得出的结果,<br />
例如:$ π_{title,year,length}(Movies) $,就是把title、year、length这三列拿出来。</p>
<table>
<thead>
<tr>
<th>title</th>
<th>year</th>
<th>length</th>
</tr>
</thead>
<tbody>
<tr>
<td>Gone with the wind</td>
<td>1939</td>
<td>231</td>
</tr>
<tr>
<td>star wars</td>
<td>1977</td>
<td>124</td>
</tr>
<tr>
<td>wayne’s world</td>
<td>1992</td>
<td>95</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>选择($σ$)<br />
就是过滤了一些元组得出的结果:<br />
例如:$ σ_{length\geq100}(Movies) $ 的结果是:</p>
<table>
<thead>
<tr>
<th>title</th>
<th>year</th>
<th>length</th>
<th>genre</th>
</tr>
</thead>
<tbody>
<tr>
<td>Gone with the wind</td>
<td>1939</td>
<td>231</td>
<td>drama</td>
</tr>
<tr>
<td>star wars</td>
<td>1977</td>
<td>124</td>
<td>scifi</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>笛卡尔积($×$)<br />
笛卡尔积又叫叉积或者直接叫积,是一个有序对的集合。就是把两个关系的元组拼在一块,组成更长的元组。例如:<br />
R:</p>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
</tbody>
</table>
<p>S:</p>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
<tr>
<td>2</td>
<td>5</td>
<td>6</td>
</tr>
<tr>
<td>4</td>
<td>7</td>
<td>8</td>
</tr>
<tr>
<td>9</td>
<td>10</td>
<td>11</td>
</tr>
</tbody>
</table>
<p>R×S:</p>
<table>
<thead>
<tr>
<th>A</th>
<th>R.B</th>
<th>S.B</th>
<th>C</th>
<th>D</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>2</td>
<td>2</td>
<td>5</td>
<td>6</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>4</td>
<td>7</td>
<td>8</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>9</td>
<td>10</td>
<td>11</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
<td>2</td>
<td>5</td>
<td>6</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
<td>4</td>
<td>7</td>
<td>8</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
<td>9</td>
<td>10</td>
<td>11</td>
</tr>
</tbody>
</table>
<p><strong>所以一个m行元组的R和一个n行元组的S的笛卡尔积有m*n个元组。</strong></p>
</li>
<li>自然连接(join,$R⋈S$)<br />
关系R和S中具有共同属性,且共同属性中值相等的元组配对构成新元组,可以一对多。</li>
<li>θ连接($R⋈{ _c}S$)<br />
其实就是带筛选条件的自然连接。</li>
<li>命名和重命名($ρ_{_{RS}(x, y, z)}(R×S)$)<br />
将关系代数生成的结果重新命名</li>
</ul>
</li>
</ul>
<h2 id="第三章-关系数据库设计理论含范式判断">第三章 关系数据库设计理论(含范式判断)</h2>
<p> 本章主要介绍关系模式中的由于存在依赖导致的问题,以及如何用范式分解去解决问题。</p>
<h3 id="1名词解释-1">1、名词解释</h3>
<ul>
<li>
<p><strong>函数依赖(Functional Dependency, FD)</strong>:函数依赖表示:若关系中的两个元组在某些属性
集合上一致,则它们在另-些属性集合上也必须一致。</p>
</li>
<li>
<p><strong>关系的键(Key of a Relation)</strong>: 关系的超键(superkey) 是可以函数决定该关系所有属
性的属性集合。若一个超键不存在任何能函数决定所有属性的真子集,则它是键。</p>
</li>
<li>
<p><strong>函数依赖的推论(Reasoning About Functional Dependency)</strong>: 存在一组规则,根据这些
规则可以推出在满足給定FD集的任意关系实例中,FD X→A成立。证明FD X→A成立的方法是计算X的闭包,使用给定FD来扩展X,直到它包含A.</p>
</li>
<li>
<p><strong>FD集合的最小基本集(Minimal Basis for a set of FD’s)</strong>:对于任何FD集合,至少有一个
最小基本集,它是一个和原FD集合等价的FD集合(即两者相互蕴涵),右边是单个属性,
而且从中去除任一一个FD或从左边去除任一个属性后都不再和原集合等价。</p>
</li>
<li>
<p><strong>Boyce-Codd范式(Boyce Codd Normal Form)</strong>:若关系中的非平凡FD指明某个超键函数
决定一个或其他多个属性,则该关系属于BCNF。BCNF的主要优点是它消除了由FD引起的冗余。</p>
</li>
<li>
<p><strong>无损连接分解(Lossless-Join Decomposition)</strong>: 分解的一个有用性质是可以通过将分解得到的关系进行自然连接,来准确地恢复原始关系。任何一个分解都包含了原关系的所有元组,但若分解选择不当,则连接结果会包含不属于原关系的元组。</p>
</li>
<li>
<p><strong>依赖保持分解(Dependency -Preserving Decomposition)</strong>:分解的另一个很好的性质是可以通过检查在分解得到的关系上的FD来证明原关系上的所有函数依赖。</p>
</li>
<li>
<p><strong>第三范式(Third Normal Form)</strong>: 有时,分解到BCNF时无法具有依赖保持性质。3NF是
一种比BCNF限制较松的范式,它允许FD X一A (其中X可以不是超键,而A是键的成
员)的存在。3NF不保证消除所有由FD引起的冗余,但大多数情况下可以消除。</p>
</li>
<li>
<p><strong>多值依赖(Multivalued Dependency, MVD)</strong>:多值依赖表示关系中有两个属性集的值以所有可
能的组合方式出现。</p>
</li>
<li>
<p><strong>第四范式(Fourth Normal Form)</strong>:关系中的MVD也可能引起冗余。4NF同BCNF相似,
但也禁止存在左边不是超键的非平凡MVD。一个关系可以无损地分解为4NF关系集台。</p>
</li>
</ul>
<h3 id="2函数依赖">2、函数依赖</h3>
<h4 id="函数依赖的定义">函数依赖的定义</h4>
<table>
<thead>
<tr>
<th> </th>
<th>A1</th>
<th>…</th>
<th>An</th>
<th>B1</th>
<th>…</th>
<th>Bm</th>
<th>-</th>
</tr>
</thead>
<tbody>
<tr>
<td>t</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>u</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
</tbody>
</table>
<p> 如上图所示,如果t、u两个元组在属性$A_1…A_n$上的值完全相同,那么他们在$B_1…B_m$上的分量值也相同,则存在函数依赖。该函数依赖形式可以记为:$A_1A_2…A_n→B_1B_2B_…B_m$,称“$A_1A_2…A_n$函数决定$B_1B_2B_…B_m$”。如果关系R中每个实例都能使一个给定的FD为真,则称R满足函数依赖$f$。通常一个FD的右边可能是单个属性,所以$A_1A_2…A_n→B_1B_2B_…B_m$也等价于:</p>
\[A_1A_2...A_n→B_1\\
A_1A_2...A_n→B_2\\
...\\
A_1A_2...A_n→B_m\\\]
<ul>
<li><strong>完全依赖</strong>:若属性集A决定B,且去除任何一个A集合中的属性后属性集A无法决定B,那么称其为完全依赖。</li>
<li><strong>部分依赖</strong>:若属性集A决定B,且去除某个A集合中的属性后属性集A仍然可以决定B,那么称其为部分依赖。</li>
</ul>
<h4 id="键超键候选键">键、超键、候选键</h4>
<ul>
<li><strong>关系的键</strong><br />
一个或多个属性集满足下面的条件:
<ol>
<li>这些属性决定其他属性,即不存在两个元组具有相同的键</li>
<li>属性集${A_1,A_2…A_n}$中没有任何属性都不能被其他属性或属性集所决定,即键是最小的。</li>
</ol>
<p>键也称为候选键</p>
</li>
<li><strong>超键</strong><br />
超键满足键的第一条规则,但是不满足第二条,因为超键不需要最小化,即属性集中只要包含了键中所有的属性就可以称为超键。超级键嘛,肯定要比键多啦。</li>
</ul>
<h4 id="函数依赖规则">函数依赖规则</h4>
<ul>
<li>
<p><strong>依赖推导</strong><br />
通过一系列的推导规则,如传递规则,从而推断出新的FD集合。对于两个FD集合S和T,若他们的FD集合集合完全相同,则认为S与T等价,若满足T中所有FD的每个关系实例也满足S中的所有FD,则认为S是从T中推导出来的。</p>
</li>
<li>
<p><strong>结合/分解</strong><br />
FD的右边可以结合,也可以分解:
分解:一个FD右边有多个属性时可以分解成左边相同,右边为单个属性。
结合:多个FD左边相同,右边可以结合为多个属性。</p>
<p><strong>注意:</strong> FD的左边不能分解,因为分解后推导可能存在条件不充分的情况而导致错误。</p>
</li>
<li>
<p><strong>平凡函数依赖</strong><br />
FD的右边是左边的子集,则称为平凡FD。如$title, year → title$,即为平凡FD。存在一种中间状态,右边部分属性在左边出现,部分没有出现,那么可以把右边在左边出现的部分删除,以此进行FD化简,这种化简规则称为平凡依赖规则。</p>
</li>
<li>
<p><strong>闭包</strong><br />
即以FD左边的属性集为基础,利用这些属性集的组合进行FD推导(FD集合需要给出),能推出不在这里面的属性,并把这些属性加入到左边的属性集中,最终得出属性集的闭包。按平凡依赖规则,左边的属性集肯定能推出他自己,所以闭包的表示为:${A_1,A_2,…A_n}^+$ 当$A_1,A_2,…A_n$是关系的超键时,${A_1,A_2,…A_n}^+$为关系的所有属性的集合。</p>
</li>
<li>
<p><strong>传递规则</strong><br />
这个就简单了,就是如果有FD:A→B,B→C,那么也有:FD:A→C。当然,这里的A、B、C泛指集合,如果C中存在某些属性在A中出现,则可以利用平凡依赖规则将这些元素除去。</p>
</li>
</ul>
<h3 id="4数据库模式设计">4、数据库模式设计</h3>
<p> 不好的数据库模式会带来冗余和相应的异常,因此一个良好的关系模式非常重要。一般都是引入范式进行分解。</p>
<h4 id="异常">异常</h4>
<p> 当一个关系中包含过多信息时,会产生冗余等问题,称之为异常,异常的基本类型有:</p>
<ul>
<li><strong>冗余</strong>:信息在多个元组中重复。</li>
<li><strong>更新异常</strong>:修改了某个元组中的信息却没有改变其他元组中相同的信息。</li>
<li><strong>删除异常</strong>:删除某元组集后导致某些属性信息丢失。</li>
</ul>
<h4 id="分解">分解</h4>
<p> 一般使用分解的方法来消除异常,分解应具有以下性质:</p>
<ul>
<li><strong>消除异常</strong>:消除冗余、更新异常、删除异常</li>
<li><strong>信息的可恢复</strong>:可以从分解后的元组中恢复原始关系</li>
<li><strong>依赖的保持</strong>:分解后的关系进行连接重构获取的原始关系仍然满足原来的FD</li>
</ul>
<h4 id="chase检验">chase检验</h4>
<p> 由于分解不能保证分解的各个关系重新连接后能得到原关系实例(就是说分解后的关系进行<strong>自然连接</strong>时得到的元组会比原来的多了或者少了),所以分解也分为无损连接和有损连接两种。chase检验就是用来检验分解是否是无损连接的。</p>
<p>算法举例:
假设有关系R(A,B,C,D)被分解为3个关系S1(A,D),S2(A,C),S3(B,C,D)。可以用以下图例来表示分解关系:</p>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
</tr>
</thead>
<tbody>
<tr>
<td>a</td>
<td>b1</td>
<td>c1</td>
<td>d</td>
</tr>
<tr>
<td>a</td>
<td>b2</td>
<td>c</td>
<td>d2</td>
</tr>
<tr>
<td>a3</td>
<td>b</td>
<td>c</td>
<td>d</td>
</tr>
</tbody>
</table>
<p>每一行代表一种关系划分,加下标的值代表可以为任意值。假定分解给定的FD是A→B,B→C,CD→A。下面进行等值替换操作。</p>
<ol>
<li>
<p>由于第一行和第二行均有相同的A值为a,考虑FD:A→B,故有b2=b1,则可替换b2为b1,得:</p>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
</tr>
</thead>
<tbody>
<tr>
<td>a</td>
<td>$\color{#fbbc05}{b1}$</td>
<td>c1</td>
<td>d</td>
</tr>
<tr>
<td>a</td>
<td>$\color{#fbbc05}{b1}$</td>
<td>c</td>
<td>d2</td>
</tr>
<tr>
<td>a3</td>
<td>b</td>
<td>c</td>
<td>d</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>由于第一行和第二行均有相同的B值为b1,考虑FD:B→C,故有c1=c,则可替换c1为c,得:</p>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
</tr>
</thead>
<tbody>
<tr>
<td>a</td>
<td>b1</td>
<td>$\color{#fbbc05}{c}$</td>
<td>d</td>
</tr>
<tr>
<td>a</td>
<td>b1</td>
<td>$\color{#fbbc05}{c}$</td>
<td>d2</td>
</tr>
<tr>
<td>a3</td>
<td>b</td>
<td>c</td>
<td>d</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>再由于第一行和第三行均有相同的CD值为cd,考虑FD:CD→A,,故有a3=a,可替换a3为a,得:</p>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
</tr>
</thead>
<tbody>
<tr>
<td>$\color{#fbbc05}{a}$</td>
<td>b1</td>
<td>c</td>
<td>d</td>
</tr>
<tr>
<td>a</td>
<td>b1</td>
<td>c</td>
<td>d2</td>
</tr>
<tr>
<td>$\color{#fbbc05}{a}$</td>
<td>b</td>
<td>c</td>
<td>d</td>
</tr>
</tbody>
</table>
</li>
</ol>
<p>由于第三步之后没有值可应用FD进行替换,且出现了一行与元组(a,b,c,d)相匹配(即出现了一行不带下标的元组)。因此必为无损,反之为有损。</p>
<p><strong>思考:为什么chase检验有效?</strong></p>
<p> 其实我们每次FD替换就相当于进行了连接操作,最后得出的元组(a,b,c,d)就是原始关系。所以能找出这个元组就是能还原原始关系了,如果找不出说明这个分解的关系都没法还原那肯定是有损的了。(之前我还有点不明白,如果出现多的怎么办呢?写到这里才幡然醒悟,根本不会存在多的情况,这个要和有损连接中出现多的数据区分开来,这里讨论的是关系。)</p>
<p><strong>思考:为什么无损连接和依赖保持不能同时存在?</strong></p>
<p> (这里我也学的不是很明白,书上只是给了一个例子,但是并没有给出证明过程,为什么这个例子不能是偶然的呢?挖坑,希望大佬解答。)</p>
<h4 id="多值依赖mvd">多值依赖MVD</h4>
<p> 多值依赖一般是出现在基于A属性两个独立属性集B,C合并在单个关系中。 其定义:
对于R中每个在所有A属性上一致的元组对t和u,能在R中找到满足下列条件的元组v:</p>
<ol>
<li>在A属性上的取值与t和u相同:</li>
<li>在B属性上的取值与t相同:</li>
<li>在R中不属于A和B的所有其他属性上的取值与u相同。</li>
</ol>
<p>说白了就是关系内存在多对多的关系,这个用第四范式解决即可。</p>
<h3 id="5范式综合">5、范式综合</h3>
<p> 范式,NF(Normal Form)。这里简要说明一下各范式的定义,详解再结合例子单独写一篇,因为这个范式分解在数据库中的地位还是很高的。注意六大范式中的BCNF为什么不叫4NF呢,因为BCNF是对3NF的加强,可以理解为3NF Plus。</p>
<p><strong>1NF:</strong></p>
<p>概念:关系中的每个属性都不可再分(消除非主属性对码的部分函数依赖)。<br />
人话:就是表头属性是一行的,不存在你在excel表里面弄个两行然后某个属性再分解一下有两种类别这样。如下就不是1NF:</p>
<table>
<thead>
<tr>
<th>标题</th>
<th>></th>
<th>标识</th>
</tr>
</thead>
<tbody>
<tr>
<td>-</td>
<td>类别</td>
<td>标签</td>
</tr>
<tr>
<td>这是标题啊</td>
<td>默认分类</td>
<td>测试标签</td>
</tr>
</tbody>
</table>
<p><strong>2NF:</strong></p>
<p>概念:在满足1NF的基础上,非主属性必须完全依赖于候选码(在1NF上消除非主属性对码的传递函数依赖)。<br />
人话:就是非主属性必须完全依赖主键属性,由于主键属性可能由多个属性组成,如果去除主键属性中某些属性后,主键属性仍然可以决定非主属性,那么就不是2NF。</p>
<p><strong>3NF:</strong></p>
<p>概念:在满足2NF的基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)。<br />
人话:设主属性为A,存在非主属性B、C(B不是A的子集),若存在A→B,B→C,那么就不是3NF。</p>
<p><strong>BCNF:</strong></p>
<p>概念:在满足第三范式(3NF)基础上,任何非主属性不能对主键子集依赖(在3NF基础上消除主属性对候选码的部分函数依赖和传递函数依赖)。<br />
人话:就是说主键属性之间也要独立,不能由某些属性决定某些属性。</p>
<p><strong>4NF:</strong></p>
<p>概念:是Boyce Codd范式,并且没有多值依赖关系(在满足BCNF的基础上,消除非平凡且非函数依赖的多值依赖)。<br />
人话:就是删除关系内的多对多关系。</p>
<p><strong>5NF:</strong></p>
<p>概念:在4NF中并且不包含任何连接依赖关系并且连接应该是无损的,关系模式R依赖均由R候选码所隐含。(4NF的基础上,消除不是由候选码所蕴含的连接依赖)。<br />
人话:就是关系之间的连接完全由主键决定,不存在其他的键与其他关系之间存在连接。</p>
<h2 id="第四章-高级数据库模型含er图">第四章 高级数据库模型(含E/R图)</h2>
<p>首先我们来看一下数据库从设计到实现的过程:</p>
<pre><code class="language-mermaid">graph LR
A(思考)-->B(高级设计)-->C(关系数据库模式)-->D[(关系DBMS)]
</code></pre>
<p>本章节主要就是讲何如设计数据库模型并能应用到关系数据库中。主要是使用了UML(统一建模语言)和E/R图(实体-联系图)对数据库关系进行设计。</p>
<h3 id="1名词解释-2">1、名词解释</h3>
<ul>
<li><strong>实体-联系模型(the Entity. Relationship Model)</strong>:在E/R模型中 描述了实体集、实体集
之间的联系以及实体集和联系的属性。实体集的成员叫做实体。</li>
<li><strong>实体-联系图(Entity-Relationship Diagram)</strong>:分别用矩形、菱形和椭圆来画实体集、联
系和属性。</li>
<li><strong>联系的多样性(Multiplicity of Relationship)</strong>:二元联系可以是一-对一、多对一或多对多。
在一对-联系中,两个实体集中的任-一个实体至多只能与另-一个实体集中的一个实体关
联。在多对- -联系中,“多”边的每个实体至多只能与另-边的-一个实体关联。多对多
联系对个数无约束。</li>
<li><strong>好的设计(Good Design)</strong>:高效地设计数据库需要忠实地表达现实世界,选择合适的元
素(如联系、属性),避免冗余一冗余是指一 -件事表示了两次,或者是用- -种间接的
或者是用过度复杂的方式表示一件事。</li>
<li><strong>子类(Subclass)</strong>: E/R模型用一个特殊的联系isa表示-一个实体集是另一个实体集的特例。
实体集可能连接在- -个层次体系中,其中每个子节点都是其父节点的特例。只要子树包
含根,那么实体集就可以有属于此层次的任意子树的组成部分。</li>
<li><strong>弱实体集(Weak Entity Set)</strong>: 需要用支持实体集的属性来确定它自己的实体。使用双边
的矩形和菱形来区分弱实体集。</li>
<li>
<p><strong>统一建模语言(Unified Modeling Language)</strong>: 在UML中,描述类和类之间的关联。类
好比E/R实体集,关联好比二元的E/R联系。特殊的多对一联系称为聚集和组合,并且这
些联系暗含了它们是如何转化为关系的。</p>
</li>
<li><strong>对象定义语言(Object Definitive Language)</strong>: 该语言用面向对象方式描述数据库的模式
设计。用户可以定义类,它有三种特性:属性、方法和联系。</li>
<li><strong>ODL联系(ODL Relationship)</strong>: ODL中的联系必须是二元的。它在其连接的两个类
(关联的两端)中通过名字来声明(同时声明其反向联系)。联系可以是多对多、多对一
或者一对一,取决于联系的类型是被声明为单个对象还是对象的集合。</li>
<li><strong>ODL的类型系统(The ODL Type System)</strong>: ODL允许构建类型,从类名和原子类型开始,
使用类型构建器为:结构、集合、包、链表、数组和字典等。</li>
</ul>
<h3 id="2er模型">2、E/R模型</h3>
<h4 id="er图的基本标注">E/R图的基本标注</h4>
<p>在实体-联系(Entity-relationship model,或er模型)模型中,数据的结构用图形化方式表示,即“实体-联系图”,用到<strong>实体集</strong>、<strong>属性</strong>、<strong>联系</strong>三个元素类型。</p>
<ul>
<li>
<p><strong>实体集</strong>
实体是某种抽象对象,相似实体的集合形成实体集。这里的实体和编程中的“对象”非常相似,但是这个实体是静态的,不具有数据操作的功能,可以想象成只有属性没有方法的对象。并且在E/R图中我们使用<strong>矩形</strong>来表示实体集。</p>
</li>
<li>
<p><strong>属性</strong>
实体集具有属性,属性是这个实体集中所具有的性质,可以想象成对象中的属性,差不多了,在E/R图中我们使用<strong>椭圆</strong>来表示属性。</p>
</li>
<li>
<p><strong>联系</strong>
联系是两个或多个实体集的连接,类似于一对一、一对多、多对多的连接关系,不过E/R图中的联系是有那么点多对多中的中间表的味道,可以单独拿出来命名。在E/R图中我们使用<strong>棱形</strong>来表示实体之间的联系。</p>
</li>
<li>
<p><strong>角色</strong>
实体集和实体集之间用边连接,这个边称之为“角色”,有时候在多路联系的情况下,我们需要给边命名,以此表示清楚实体集与实体集或实体集与自己本身的关系。边的末端可能会出现箭头,这个箭头表示这一端<strong>最多只能存在一个</strong>。</p>
</li>
<li>
<p><strong><em>E/R图基本示例</em></strong></p>
<pre><code class="language-mermaid">graph TB;
A[Stars]---D{Contracts};
B[Movies]---D;
D-- Studio of star -->C[Studios];
D-- Producing studio -->C[Studios];
a1([title])---A;
a2([year])---A;
a3([length])---A;
b1([name])---B;
b2([address])---B;
C---c1([name]);
C---c2([address]);
</code></pre>
</li>
</ul>
<h4 id="设计原则">设计原则</h4>
<ul>
<li>
<p><strong>忠实性</strong>:设计应当忠实于应用的具体要求。就是说建模原则是要符合现实世界规则,总不可能真把三头六臂插在人身上吧,这比喻也不是很恰当,头和臂是属性的话,三、六就是数据了,那后期装个机械手臂也是可以的啊哈哈。换个比喻,你对花草树木建模,那就不能说他拥有头了吧。</p>
</li>
<li>
<p><strong>避免冗余</strong>:应当小心对每件事只说一次(我觉得这书里面很多翻译真的很头疼啊)。前面好像也介绍过很多关于冗余的事了,这里不再重复了,“避免冗余”这个字面意思挺好理解的。</p>
</li>
<li>
<p><strong>简单性</strong>:除非有绝对需要,不要在你的设计中添加更多的成分。</p>
</li>
<li>
<p><strong>选择正确的关系</strong>:实体集可以用可以用多种联系连接起来,但是不要把每种可能的联系都写进来。</p>
</li>
<li>
<p><strong>选择正确的元素种类</strong>:对于特定的情形,有的东西适合做实体,有的东西适合做属性,不要搞混,不然会出现异常。例如本书中的电影公司就适合做实体,如果做属性会出现更新异常或者删除异常。</p>
</li>
</ul>
<h4 id="er图标注进阶">E/R图标注进阶</h4>
<ul>
<li>
<p><strong>isa子类联系</strong>
在E/R图中,可以使用isa作为一种子类联系。我们使用三角形来表示。(mermaid语法画不了三角形图,我这里就不举例了,书上看看吧,就是把联系的棱形改为三角,然后名字都是isa)。子类的作用就自行联系面向对象中的子类吧。</p>
</li>
<li>
<p><strong>键</strong>
每个实体集必须有一个键,一个实体集可以有多个键,isa中根实体集有用键所需的所有属性。这里我们对属性使用下划线来表示。</p>
</li>
<li>
<p><strong>引用完整性</strong>
假设R是从实体集E到实体集F的联系,用圆箭头指向F,表示此联系从E到F中F实体必须存在。</p>
</li>
<li>
<p><strong>度约束</strong>
在实体和联系的边上加上一个数字表示相关实体集中数目的约束,当然,可以用判断符号,如“<=10”。</p>
</li>
<li>
<p><strong>弱实体集</strong>
一个实体集键由另一个实体集的部分或全部属性构成,这样的实体集称为弱实体集。我们用<strong>矩形套矩形</strong>的图形来表示这种实体集,边上的联系用<strong>棱形套棱形</strong>的图形表示(禁止俄罗斯套娃!!!)。例如:一个电影公司有几套拍摄班子命名为A、B、C,但是其他公司也有可能用同样的方式命名A、B、C,这时候命名不能作为拍摄班子的键,必须要同时给出电影公司才能确定一套拍摄班子,称这样的实体集为弱实体集(很作是吧,但是转换到数据库表里面我们用一个id主键就能把他变成非弱实体集,id还是万金油啊)。</p>
</li>
</ul>
<h4 id="er图到关系设计">E/R图到关系设计</h4>
<p>初看起来,把er设计转换为关系数据库模式很直观:</p>
<ul>
<li>每个实体集可以转化为具有相同属性集的关系;</li>
<li>联系也用关系替换,替换的关系属性就是联系所连接的实体集的键集合。</li>
</ul>
<p>虽然这两条规则在大多数情况下可用,但仍要考虑下面几种特殊情况:</p>
<ol>
<li>弱实体集不能直接转化为关系。</li>
<li>“isa” 联系和子类要特殊对待。</li>
<li>有时,需要把两个关系组合为一个关系,特别是当-个关系是从实体集E转化形成,而
另一个关系是由E到其他实体集的一个多对一的联系转化而来时,要考虑这种组合。</li>
</ol>
<p>下面讨论关系的具体转换:</p>
<ul>
<li><strong>实体集</strong>:直接把关系名和属性写出来,比如Stars(name, address)。</li>
<li><strong>联系</strong>:把联系两端的键以及联系自身的属性放一块,关系名就是联系名,如果出现属性重复,那就重新命名,比如name→studio_name。</li>
<li><strong>关系组合</strong>:有时候按照上面两种方法出来的关系可能不是很好,存在查询效率低的问题,这种情况经常出现在多对一和一对一的情况。这时我们可以把一的一边的主键放到多的一边而不需要再建立一个中间表降低查询效率了。</li>
<li><strong>弱实体集</strong>:当E/R图中有一个弱实体集时,需要做下面三件不同的事:
<ol>
<li>从弱实体集W得到的关系不仅要包含W的属性,还要包含相应支持实体集的键属性。支
持实体集很容易辨认,因为它们由从W引出的支持联系(双边菱形)连接。</li>
<li>与弱实体集W相连的联系,经转化后所得的关系必须包含W键属性,以及对W键有贡献
的实体集属性。</li>
<li>然而,一个支持联系R (它是从弱实体集W指向支持实体集)不必被转化为关系,因为由多对一联系R转化得到的关系的属性可以是W的属性,也可以与W的关系模式进行组合(在R有属性的情况下)。</li>
</ol>
</li>
<li><strong>isa子类</strong>:isa子类转换有三种观点:
<ol>
<li>遵照er观点:即父类子类都创建关系,子类要包含父类的键,如B、C是A的子类,那就创建A、B、C三个子类,而且B、C中有A的键。</li>
<li>把实体看做属于单个类的对象:就是每个类都创建一个关系,父类子类都要,而且重复的子类还得加起来弄一个,并且子类拥有父类的所有属性。</li>
<li>使用空值:就是父类子类属性放一块,然后没有的属性给null就行了。
以上三种方法各有各的优缺点,有的冗余多,有的查询效率高,就看实际应用选择了。</li>
</ol>
</li>
</ul>
<h3 id="3uml建模">3、UML建模</h3>
<h4 id="uml简介">UML简介</h4>
<p> UML (统一建模语言,Unified Modeling Language)最初是开发用来在面向对象风格中作为描述软件设计的一种图形化的标注。它现在已经做了一些扩展和更改,可以作为一种流行的数据库设计描述的标注。</p>
<h4 id="uml标注">UML标注</h4>
<p> UML中的类与er模型中的实体集类似,但是图形标注上有很大差别。如下图显示了与er实体集Movies对应的类。一个类框分为三个部分。顶部是类的名字,中间是它的属性,就像是一个类的实例变量一样。在Movies类中,有title, year, length和genre属性,属性后面加PK代表键。底部是方法。er模型和关系模式都不提供方法。然而这是一个重要的概念,通常出现在现代的关系系统中,称作“ 对象关系”DBMS。</p>
<pre><code class="language-mermaid">classDiagram
class Movies{
title PK
year PK
length
withdrawl
method1()
method2()
}
</code></pre>
<h4 id="uml关联">UML关联</h4>
<ul>
<li>
<p><strong>普通关联</strong>:两个类之间画条线,联系名写线下面。连接端点用两个数字如<code class="language-plaintext highlighter-rouge">0..*</code>表示这端的对象数量约束上下限。<code class="language-plaintext highlighter-rouge">*</code>表示无限制,没有标签代表<code class="language-plaintext highlighter-rouge">1..1</code>,注意两个端点都要写。如下图:</p>
<pre><code class="language-mermaid">classDiagram
Studios "0..1" -- "0..*" Movies : Owns
Stars "0..*" -- "0..*" Movies : Stars-in
</code></pre>
</li>
</ul>
<p><strong>注意</strong> 这里的数量约束已经表达了我们前面说的E/R图中的弯箭头的完整性约束。</p>
<ul>
<li>
<p><strong>自关联</strong>:一个关联的两端可以连接同一个类,这样的关联称为自关联。为了区分一个他在自关联中表现的不同角色,还要给两端分别起个不同的名字。</p>
</li>
<li><strong>关联类</strong>:在两个类之间的线上加一个类,就称作关联类,和E/R图的也很类似。</li>
<li><strong>子类</strong>:UML允许一个类C有4个不同的子类:
<ol>
<li>完整对局部(Complete versus Partial)。 每个在类C中的对象是否是某个子类的一个成员?如果是,子类是完整的;否则,它们是局部的或者不完整的。</li>
<li>分离对重叠(Disjoint versus Overlapping)。 子类是分离的(Disjoint) (一个对象不能在两个子类中)吗?如果-一个对象可以在两个或多个子类中,那么子类可以称为是重叠的(Overlapping)。</li>
</ol>
<p>简单来说,完整对局部,说的就是父类和子类的关系,一般父类的对象可能是局部的,子类对象是完整的,因为父类可能会少一些子类的属性。分离对重叠就是子类和子类之间的关系,如果一个对象可以被两个子类表达那就是子类重叠(设想一下属性相同而方法不同?)。<br />
子类的表示方法为:连一条线到父类,在父类端画个空三角箭头。</p>
</li>
</ul>
<h4 id="聚集与组合">聚集与组合</h4>
<p> 对于多对一的关联有两个特殊的标记,它们的含义相当微妙。–方面,它们影响了面向
对象的编程风格,通常对于一个类在它的属性之间都有到其他类的引用。另一方面,这些特殊的标注确实约束图如何转化为关系。</p>
<ul>
<li><strong>聚集(aggregation)</strong>:在两个类之间的一条线,这条线的末端是一个空的菱形。菱形的含义是那端的标注一定要为0..1, 也就是说,聚集是-一个从这端的类到菱形端类的多对一的关联。尽管聚集是一个关联,但不必对它命名,因为名字在关系的实现中不会被用到。</li>
<li>
<p><strong>组合(composition)</strong>:与关联相似,但在菱形端的标注一定要为1..1。 也就是说,与菱形对应相连的类的每个对象都要与菱形端的-一个对象相连接。组合的菱形是实心的黑色。
如下图:</p>
<pre><code class="language-mermaid">classDiagram
classA "1..*" --o classC : aggregation
classB "0..1" --* classD : composition
</code></pre>
</li>
</ul>
<h3 id="4uml图到关系设计">4、UML图到关系设计</h3>
<ul>
<li><strong>类</strong>:对于每个类,创建一 个关系,关系的名为这个类的名字,关系的属性就是这个类的属性。</li>
<li><strong>关联</strong>:对于每个关联,创建-一个名字为关联名的关系。关系的属性是两个连接类的键属性。如果恰巧两个类的属性名字相同,那么适当地重命名它们。如果有一个关联类附在这个关联上,那么在这个关系的属性中包含那个关联类的属性。</li>
<li><strong>子类</strong>:如果层次的每一层都是分离的,那么建议使用面向对象方法。如果层次在每一层既是完整的又是分离的,那么任务相对简单。如果使用面向对象的方法,那么只要为层次中叶子节点的类构建关系。如果层次很大并且在某些或者所有的层上是重叠的,那么er方法是合适的。可能会需要很多的关系使得关系数据库模式变得臃肿。</li>
<li><strong>聚集与组合</strong>:聚集与组合都是多对一类型的关联,在关系数据库模式中表示它们的方法可以是像普通关联做的那样进行转换。但是有些聚集与组合的实现会特殊一点,比如非棱形端是一个类的子类。所以建议聚集与组合用如下方式处理:不为聚集与组合构建任何关系,为非菱形端的类添加菱形端类的键属性。在聚集情况下(不是组合),这些属性可以为空。</li>
<li><strong>弱实体集</strong>:在UML的标注中没有提到与er模型中双边标注相对应的弱实体集。其实没有什么必要。
原因是,与er不同,UML遵循面向对象系统的传统,每个对象都有自己的对象标识。所以化关系的时候和关联化关系没啥区别。如果一定要标识一下,那就拿出来做个类,然后在端点加个小方块写上PK二字即可。</li>
</ul>
<h3 id="5odl语言">5、ODL语言</h3>
<p> ODL (对象定义语言,Object Definition Language)是- -种基于文本的使用面向对象术语描述数据库结构的语言。像UML一样,类是ODL中的核心概念。就像UML类一样, ODL中的类具有名字、属性和方法。联系与UML的关联类似,但是在ODL中它不是一个独立的概念,而是可以作为特征的附加成员嵌入到类中。<br />
这个东西和编写模型代码类差不了多少,这里就不多BB了。</p>
<h2 id="第五章-代数和逻辑查询语言">第五章 代数和逻辑查询语言</h2>
<p> 前面介绍过简单地代数查询语言,这章扩展到包上进行操作,并且引入Datalog进行查询。</p>
<h3 id="名词解释">名词解释</h3>
<ul>
<li><strong>分组和聚集(Grouping and Aggregation)</strong>: 聚集操作对关系的一列加以汇总。典型的聚
集操作是求和、求平均、求最小值和最大值。分组操作算符允许对某个关系的元组按着
它们在一个或者多个属性上的值分组,然后进一步 对每个组进行聚集计算。</li>
<li><strong>外连接(Outerjoin)</strong>: 两个关系的外连接是先执行这两个关系的连接。然后,那些悬浮
的元组(不能跟任何元组匹配的元组)用oul1值补齐后,也加入到结果当中。</li>
<li><strong>Datalog</strong>:这种逻辑形式允许在关系模型上编写查询。在Datalog中, 可以编写规则,规
则的头部谓词或关系根据子目标组成的主体来定义。</li>
<li><strong>原子(Atom)</strong>:规则头部和子目标都是原子,原子由一个应用于若千个参数(可选为否
定)的谓词组成。谓词可以表示关系或算术比较(例如<)。</li>
<li><strong>IDB和EDB谓词(IDB and EDB Predicate)</strong>: 某些谓词对应于已存储的关系,被称为
EDB (扩展数据库)谓词或称为关系。另-种谓词称为IDB (内涵数据库)谓词,是由
规则定义的。EDB谓词可以不出现在规则头部。</li>
<li><strong>安全规则(Safe Rule)</strong>:一般说Datalog规则是 安全的,是指规则中每个变敏都出现在主体的.
些非否定关系子日标中。安全规则保证:如果EDB关系是有限的,那么IDB关系也将是有限的。</li>
</ul>
<h3 id="包上的关系操作">包上的关系操作</h3>
<h4 id="概念">概念</h4>
<p> 把关系看做包(多集,multiset)而不是集合的话,一个元组就可以在关系中多次出现。</p>
<h4 id="为何采用包">为何采用包?</h4>
<p> 商业DBMS实现的关系都是包而不是集合,因为基于包的关系会使得一些关系操作的实现效率更好,例如:</p>
<ul>
<li>两个包关系的并操作,可以简单地将一个关系的所有元祖复制到另一个关系,而不必消除两个关系当中的重复元组。</li>
<li>当在集合关系上投影时,需要逐个比较确保投影中相同元组只出现一次,而基于包就可以简单地投影每个元组加入到结果中而不必做比较操作,数据量大的时候可以节约大量的操作时间。</li>
</ul>
<h4 id="关系操作">关系操作</h4>
<p> 包的关系操作也有并、交、差、投影、选择、笛卡尔积、连接等操作,与前面提到的操作也是极为相似,唯一不同的是包中允许重复,所以可以把包中重复的元组看做是两个不同的元组进行操作,这样的话操作过程就和集合的操作没有什么区别了。</p>
<h3 id="关系代数的扩展操作符">关系代数的扩展操作符</h3>
<h4 id="消除重复δ">消除重复(δ)</h4>
<p> 消重复操作符(duplicated-elimination operator)δ把包中的重复元素去掉,只保留一个
拷贝在关系当中。有时候需要用一个算子把包转化为集合。为此用δ(R)来返回一个没有重复元组的关系R。</p>
<h4 id="聚集sumavgminmaxcount">聚集(SUM、AVG、MIN、MAX、COUNT)</h4>
<p> 聚集操作符(aggregation operator),例如求和或者求平均值。这些不是关系代数的操
作,但却是被分组(grouping) 操作符所使用(下面会讲到)的操作。聚集操作符应用到关系的属性(列)上, 比如说是求和操作,就把这一列的所有值加起来求和计算出结果。</p>
<ul>
<li><strong>SUM</strong>产生一列的总和,得到的是一个数字值。</li>
<li><strong>AVG</strong>产生一列的平均值,结果也是数字值。</li>
<li><strong>MIN和MAX</strong>,当用于数字值列的时候,分别产生这一列中最小和最大值;当应用于字符值列的时候,分别产生的是字典序的第一个和最后一个值。</li>
<li><strong>COUNT</strong>产生一列中的“值”的数目(并不一定指不同的值)。同样,COUNT应用于一个关系的任何一个属性的时候,产生的是这个属性的元组的数量,包括重复的元组。</li>
</ul>
<h4 id="分组γ">分组(γ)</h4>
<p> 分组操作(grouping) 根据元组在一个或者多个属性上的值把关系的元组拆分成“组”。这样,聚集操作就是对分好组的各个列进行计算。这给我们提供了在经典关系代数表达式中不能表达的多个查询的描述方式。分组操作符(grouping operator) γ是组合了分组和聚集操作的一个算子。下面给出一个分组的例子:</p>
\[γ_{starName,MIN(year)→minYear,COUNT(title)→ctTitle}(StarsIn)\]
<p>这里就是在StarsIn表中以starName分组,分组后计算每个组中最小的year并重命名为minYear,统计title的数量并重命名为ctTitle。</p>
<p><strong>思考</strong> δ操作其实是γ操作的特殊情况<br />
<strong>思考</strong> 分组前进行排序会不会有性能上的提升</p>
<h4 id="扩展投影abx">扩展投影(A+B→X)</h4>
<p> 扩展投影(extended projecion)是普通π操作(投影操作)符上增加了一些增强功能的算子。它可以将变量关系的列作为参数进行计算,并产生新的列。其实就是在普通投影操作上加了→符号,以此对关系上的各个分量进行计算操作并产生新的投影。如:</p>
\[π_{A,B+C→X}(R)\]
<p>上面就是一个将R进行扩展投影的操作,A列直接投影,B列加C列相加投影成X。</p>
<h4 id="排序算子τ">排序算子(τ)</h4>
<p> 排序算子(sorting operator) τ把一个关系变成一个元组的列表,并根据一个或者多个
属性来排序。这个操作使用时要心中有数,因为一些关系代数操作不能作用在列表上。选择
操作或投影操作可以对列表运算,并且其结果还保持列表中元素的顺序输出。例如:</p>
\[τ_{C,B}(R)\]
<p>上面就是在关系R上对C进行排序,如C相同则按B来排序。</p>
<h4 id="外连接">外连接</h4>
<p> 外连接算符(outerjoin operator) 是连接算符的变体,它防止了悬浮元组的出现。悬浮元组的存在有时不能完整表示关系,在外连接的结果中,悬浮元组用null符号⊥补齐,这样悬浮元组就可以在结果当中被表示出来。</p>
<p>外连接共有三种(完犊子,外连接符打不出来?这里用英文表示了,具体符号看书吧):</p>
<ul>
<li>R OUTER JOIN S:R和S做自然连接,然后把R和S中的元组加入到结果,不存在的值用⊥符号填充</li>
<li>R LEFT JOIN S:R和S做自然连接,然后把左边R中的元组加入到结果,不存在的值用⊥符号填充</li>
<li>R RIGHT JOIN S:R和S做自然连接,然后把右边S中的元组加入到结果,不存在的值用⊥符号填充</li>
</ul>
<h3 id="关系逻辑">关系逻辑</h3>
<p> 作为另外一种基于代数的抽象查询语言,可以用逻辑形式来表示查询。逻辑查询语言Datalog (database logic) 由if-then规则组成。 这些规则表示:从某个关系的特定元组的组合可以推断出另一些元组必定满足另一关系,或者满足查询的结果。</p>
<h4 id="谓词和原子">谓词和原子</h4>
<p> 关系在Datalog中由谓词(predicate) 表示。每个谓词拥有固定数目的参数,一个谓词和它的参数一起被称为原子(atom)。原子的语法就像传统编程语言中函数调用的语法。例如,$P(x1,x2, .. xn)$即是一个由谓词P和参数$x1, x2, .. xn$组成的关系原子。算术原子是对两个算术表达式作比较,例如$x<y$或$x+1≥y+4*z$。算术原子和关系原子都将所有出现在原子中的变量值作为参数,并且都返回一个布尔值。</p>
<p> 实质上谓词就是一个返回布尔值的函数名。如果R是一个包含n个固定顺序的属性的关系,那么也可以用R作为对应这个关系的谓词名。如果$(a1, a2,… an)$是满足R的元组,那么原子$R(a1, a2, .,. an)$的值为TRUE,否则原子的值为FALSE。注意,谓词定义的关系是通常都是用来处理集合关系,把Datalog扩展到包时操作相同,不过就是重复的元组不进行消除。
例如:</p>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
</tbody>
</table>
<p>那么R(1, 2)、R(3, 4)为TRUE,其他任意R(x, y)都为FALSE。</p>
<h4 id="datalog规则和查询">Datalog规则和查询</h4>
<p> 与经典关系代数类似的操作在Datalog中称作规则(rule), 它包括:</p>
<ol>
<li>一个称为头部(head) 的关系原子:</li>
<li>符号$ \leftarrow $,经常读作“if” ;</li>
<li>主体(body) 部分,由一个或多个称为子目标(subgoa]) 的原子组成。原子可以是关
系原子或算术原子。子目标之间由AND连接,任何子目标之前都可随意添加逻辑算子NOT。</li>
</ol>
<p>例如:<br />
\(LongMovie(t,y)←Movies(t,y,l,g,s,p) \quad AND \quad l≥100\)</p>
<p><strong>注意</strong>:Datalog中如果某个变量只出现以此,那么可以简写为<code class="language-plaintext highlighter-rouge">_</code>,所以以上规则等同于:<br />
\(LongMovie(t,y)←Movies(t,y,l,\_,\_,\_) \quad AND \quad l≥100\)</p>
<h4 id="datalog规则">Datalog规则</h4>
<p> 在规则中使用变量还是有限制的,该限制是要使得一条规则的结果是一个有限的关
系,从而包含算术子目标或否定(negated) 子目标(前面有NOT算子)的规则具有直观的意义,
这个限制条件称作安全(safety) 条件,即:</p>
<ul>
<li>每个在规则中任意位置出现的变量都必须出现在主体的某些非否定的关系子目标中。</li>
</ul>
<p>尤其是任何在规则头部、否定关系子目标或任意算术子目标中出现的变量,也必须出现在主体的非否定的关系子目标中。</p>
<p>例如,下面的规则有三处违反安全条件:</p>
\[P(x,y)←Q(x,z) AND NOT R(w,x,z) AND x<y\]
<ol>
<li>变量y出现在头部但不在主体的任何非否定关系子目标中。注意,出现在算术子目标
$x<y$中的y并不能有助于把y的值限定在有限集合内。只要对应w, x, z值的a, b, c满足前两个子目标,就必须增加无限多个d>b元组(b, d)到头部关系P。</li>
<li>变量w出现在-一个否定的关系子目标中,但不在非否定的关系子目标中。</li>
<li>变量y出现在一个算术子目标中,但不在非否定的关系子目标中。</li>
</ol>
<p><strong>其实就是说凡是你用来做原子操作的变量或者在头部出现的变量就必须出现在没有带否定NOT的关系当中。</strong></p>
<h4 id="扩展谓词和内涵谓词">扩展谓词和内涵谓词</h4>
<ul>
<li>扩展谓词(Extensional predicate):这种谓词的关系存放在数据库中。</li>
<li>内涵谓词(Intension predicate):这种谓词的关系是由一一个或多个Datalog规则计算出来。</li>
</ul>
<p><strong>区别</strong>:</p>
<p> 这两种谓词之间的区别等同于关系代数表达式的操作数与关系代数表达式计算出的关系之间的区别。前者,关系代数表达式操作数是“可扩展的”(也就是说,通过它的扩展定义关
系,也即“ 关系的当前实例”的另一命名);后者,关系代数表达式计算出的关系可以是最终
结果,也可以是对应某些子表达式的中间结果,这些关系是“内涵的”(即是由程序员的“意
图”决定)。</p>
<h3 id="关系代数与datalog">关系代数与Datalog</h3>
<p> 基本关系代数描述的每一个表达式都可以用Datalog查询表达。而在扩展关系代数中的操作,如分组和聚集,则不能用Datalaog表达。类似地,Datalog不支持包操作,比如消重复。任何单个Datalog规则都可以用关系代数表达。也就是说,用基本关系代数表达的查询产生的元组与这个Datalog规则产生的头部关系元组相同。可是,当考虑Datalog规则集合时,情况有所变化。Datalog可以表达递归,而关系代数不可以。</p>
<h2 id="第六章-数据库语言sql">第六章 数据库语言SQL</h2>
<p> 本章介绍SQL的基础查询语言和数据库修改语句。同时还要介绍数据库系统的基本工作单位——“事务” 的概念。这些内容虽然比较粗略,但也能让读者对数据库操作的互相影响及其引起的问题有一个初步的认识。对于本章节的学习,如果平时写代码和数据库打交道比价少的看起来吃力的话,建议还是结合上机与书本例子进行学习(像我这种接触过又不很熟的半吊子看起来还是不吃力的:stuck_out_tongue_closed_eyes:)。<br />
下面给出一个书上的数据库模式的例子,后面的SQL操作全部围绕这个模式进行:</p>
<blockquote>
<p>Movies (title, year, length, genre, studioName, producerC#)
StarsIn (movieTitle, movieYear, atarNane)
MovieStar (name, address, gender, birthdate)
MovieExec(name, addres, cert#, netHorth)
Studio(name, address, presC#)</p>
</blockquote>
<h3 id="名词解释-1">名词解释</h3>
<ul>
<li><strong>SQL</strong>:SQL语言是关系数据库系统使用的主要查询语言。目前最新标准是SQL-99或SQL3。商业系统通常和SQI标准略有出入。</li>
<li><strong>select-from-where查询(select-from- where Query)</strong>: SQL查询最常用的形式是select-
from-where。它允许使用几个关系的积(在FROM子句中)、对结果元组施加过滤条件(在WHERE子句中),并产生需要的字段(SELECT子句)。</li>
<li><strong>子查询(Subquery)</strong>: sclect- from where查询也能在其他查询中的WHERE子句或F ROM子句中作为子查询使用。操作符EXISTS、IN、 ALL和ANY都可以作用在WHERE子句中子查询形成的关系_上,并形成布尔值表达式。</li>
<li><strong>关系上的集合运箅(Set Operation on Relation)</strong>: 可以通过使用保留字UNION、 INTERSECT和EXCEPT分别连接关系或者产生关系的查询,达到实现关系的并、交和差的操作。</li>
<li><strong>连接表达式(Join Expression)</strong>: SQL提供如NATURAL JOIN这 样的操作符作用在关系上,可以看作将其看作是一一个查询或在F ROM子句中定义的-一个新关系。</li>
<li><strong>空值(Nul] Value)</strong>: SQL在元组的字段值没有指定具体值的时候,提供-一个特殊的值NULL。NULL的逻辑和算术运算规则都比较特殊。任何值和NULL值比较的结果都是布尔值UNKNOWN,即使该值也是NULL值。UNKNOWN值也能在布尔运算中出现,但把它看作处于TRUE和FALSE之间的-一个值。</li>
<li><strong>外连接(Outerjoin)</strong>: SQL提供一个OUTER JION操作符连接关系。 连接结果中包括来自连接关系的悬浮元组。结果关系中悬浮元组被填上NULL值。</li>
<li><strong>关系的包模型(the Bag Model of Relation)</strong>: SQL实际上把关系看作装满元组的包而不是元组的集合。可以使用DISTINCT保留字来消除元组重复,而保留字ALL在某些不认为关系是包的情况下允许结果是包。</li>
<li><strong>聚集(Aggregation)</strong>:关系中某列的值可以通过使用保留字SUM、AVG (平均值)、MIN、MAX和COUNT进行统计(聚集)。 在进行聚集操作前元组可以通过GROUP BY进行分组。 利用保留字HAVING可以消除某些分组。</li>
</ul>
<h3 id="数据类型">数据类型</h3>
<p>先介绍SQL系统支持的基本数据类型。关系中所有的属性都必须有一个数据类型。</p>
<ol>
<li>
<p><strong>可变长度(VARCHAR)或固定长度字符串(CHAR)</strong>:类型CHAR(n)表示最大为n个字符的固定长度字符串。VARCHAR(n)也表示最多可有n个字符的字符串。它们的区别与具体实现有关。一般来说,CHAR类型会以一些短的字符串来填充后面未满的空间来构成n个字符,而VARCHAR会使用一个结束符或字符长度值来标志字符串的结束,后面未满的空间不会做填充。</p>
</li>
<li>
<p><strong>固定或可变长度的位串(BIT)</strong>:位串和字符串类似,但是它们的值是由比特而不是字符组成。
类型BIT(n)表示长为n的位串。BIT VARYING(n)表示最大长度为n的位串。</p>
</li>
<li>
<p><strong>逻辑类型BOOLEAN</strong>:该类属性的可能值是TURE、FALS E和UNKNOWN。</p>
</li>
<li>
<p><strong>整数INT和INTEGER (两者为同义词)</strong>:类型SHORTINT也表示整数,但是表示的位数可能小些,具体取决于实现。(类似C语言中的int和short int)。</p>
</li>
<li>
<p><strong>浮点值FLOAT和REAL (两者为同义词)</strong>:需要高精度的浮点类型可以使用DOUBLE、PRECISION。SQL还提供指定小数点后位数的浮点类型。例如DECIMAL(n, d)允许可以有n位有效数字的十进制数,小数点是在右数第d位的位置。例如0123. 45就是符合类型ECIMAL(6, 2)定义的数值。NUMERIC几乎是DECIMAL的同义词,尽管它们存在某些依赖于实现的小差别。</p>
</li>
<li>
<p><strong>日期(DATA)和时间(TIME)</strong>:这些值本质上是字符串的–种特殊形式。实际上可以把时间和日期强制转换成字符串类型。反之也可以把某些特定格式字符串转换为日期和时间。</p>
</li>
</ol>
<h3 id="简单查询">简单查询</h3>
<p> SQL中最简单的查询是找出关系中满足特定条件的元组,这种查询和关系代数中的选择操作类似。和几乎所有的SQL查询类似,简单查询使用代表SQL特点的三个保留字SELECT、FROM和WHERE来表示。例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span>
<span class="k">FROM</span> <span class="n">Movies</span>
<span class="k">WHERE</span> <span class="n">studioName</span><span class="o">=</span><span class="s1">'Disney'</span> <span class="k">AND</span> <span class="nb">year</span> <span class="o">=</span> <span class="mi">1990</span><span class="p">;</span>
</code></pre></div></div>
<ul>
<li>FROM 后面的是关系名;</li>
<li>WHERE 后面的是查询条件,就像关系代数中的选择,当然where可以省略,这样查的就是所有;</li>
<li>SELECT 后面是选择结果中出现的属性,其实就是投影,<code class="language-plaintext highlighter-rouge">*</code>代表所有属性;</li>
</ul>
<p> 所以上面的查询语句就是:从Movies表中查询工作室名为Disney并且年份为1990的元组并且显示所有的属性。</p>
<p><strong>Tips1</strong>: 发现上面的一丝丝不对劲了没?我们写语法的时候是SELECT、FROM再WHERE,但是我们解释的时候是FROM再WHERE再SELECT。有没有发现对于我们来理解的话后面这种顺序会好一些呢?至于为什么写的时候要按前面顺序写?大概是为了符合代数查询的顺序吧。</p>
<p><strong>Tips2</strong>:SQL语法中大小写是不区分的,所以大小写都是正确的语法,不过习惯上进行大写便于阅读(虽然不区分大小写但是只有大写是保留字而其他的如from、fRom这些都不是保留字,至于区别是啥目前还没遇到,以后再补充)!</p>
<ul>
<li>
<p><strong>投影</strong>:</p>
<p>在SELECT后面把关系中具体的属性列出来就是投影操作了。而且对这些投影的属性还可以进行一些简单计算和重命名操作。比如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span> <span class="k">length</span> <span class="o">*</span> <span class="mi">0</span><span class="p">.</span><span class="mi">016667</span> <span class="k">AS</span> <span class="n">lengthHours</span>
<span class="k">FROM</span> <span class="n">Movies</span><span class="p">;</span>
</code></pre></div> </div>
<p>以上就是把title、length投影出来并且把length*0.016667后更名为lengthHours</p>
</li>
<li>
<p><strong>选择</strong>:</p>
<p>关系代数中的选择操作符在SQL中是通过SQL的WHERE子句表示。WHERE 子句中的表达式(包括条件表达式)和普通的计算机语言(如C和Java)中的表达式类似。可以通过值比较运算来建立表达式,比较运算使用六个常用的比较运算符: <code class="language-plaintext highlighter-rouge">=、<>. <、>、<=和>=</code>。最后四个运算符与C语言中的相同,而<>在SQL中表示“不等于”(在C语言中是!=),=在SQL中表示“等于”(在C语言中是==)。例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">title</span>
<span class="k">FROM</span> <span class="n">Movies</span>
<span class="k">WHERE</span> <span class="p">(</span><span class="nb">year</span> <span class="o">></span> <span class="mi">1970</span> <span class="k">OR</span> <span class="k">length</span> <span class="o"><</span> <span class="mi">90</span><span class="p">)</span> <span class="k">AND</span> <span class="n">studioName</span> <span class="o">=</span> <span class="s1">'MGM'</span> <span class="p">;</span>
</code></pre></div> </div>
</li>
<li>
<p><strong>字符串比较</strong>:</p>
<p>当使用如<或>=等比较运算符对字符串作比较运算时,实际上比较的是它们的词典顺序
(如字典顺序或字母表顺序)。也就是说,如果字符串SA:a1a2 .. an和字符串SB:b1b2... bm是两个字符串,当$a_1<b_1$时,或者$a_1=b_1$且$a_2<b_2$时,如此下去,则SA小于SB。如果$n<m$并且$a1a2...an=b1b2...bn$,则称字符串SA小于SB,也就是说第一个字符串正好是第二个字符串的一个前缀。</或></p>
</li>
<li>
<p><strong>模式匹配</strong>:</p>
<p>SQL也提供了一种简单的模式匹配功能用于字符串比较,类似糊查询,如<code class="language-plaintext highlighter-rouge">s LIKE P</code>或<code class="language-plaintext highlighter-rouge">s NOT LIKE P</code>。其中s是一个字符串,p是模式(pattern), 即一个可能使用了两个特殊字符<code class="language-plaintext highlighter-rouge">%</code>和<code class="language-plaintext highlighter-rouge">_</code>的字符串(如果要查询的字符串中含有%或_,则需转义,而且’‘代表一个’)。p中普通字符仅能匹配s中与其相同的字符,而%能匹配s中任何任意长度(包括零长度)的字符串, p中的_则能匹配s中任何一个字符。该表达式的值为真当且仅当字符串s匹配模式p。下面是用ESCAPE保留字用来指定转义的例子(SQL中没有指定转义字符,所以用ESCAPE代替):</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">s</span> <span class="k">LIKE</span> <span class="s1">'x%%x%'</span> <span class="k">ESCAPE</span> <span class="s1">'x'</span>
</code></pre></div> </div>
</li>
<li>
<p><strong>日期时间</strong>:</p>
<p>SQL的实现版本通常将时间和日期作为特殊的数据类型。其值常常用不同形式表示,如05/14/1948或14 May 1948。</p>
</li>
<li>
<p><strong>空值</strong>:</p>
<p>SQL允许属性有一个特殊值NULL,称作空值。对于空值有许多不同的解释:未知值、不适用的值、保留的值。在WHERE子句中,要考虑元组中的空值可能带来的影响。当对空值进行运算时有两个重要的规则要记住。</p>
<ol>
<li>对NULL和任何值(包括另-一个NULL值)进行算术运算(如x和+),其结果仍然是空值。</li>
<li>当使用比较运算符,如=或>,比较NULL值和任意值(包括另一个NULL值)时,结果都为UNKONWN值。值UNKNOWN是 另外一个与TRUE和FALSE相同的布尔值。后面将简介地介UNKNOWMN值的操作。可是,要记住,虽然NULL也是一个可以出现在元组中的值,但是它不是一个常量。因此,虽然可以利用上面的规则对值为NULL的表达式进行运算,但是不可以直接将NULL作为一个操作数。</li>
</ol>
</li>
<li>
<p><strong>布尔值</strong>:</p>
<p>比较运算结果要么是TRUE要么是FALSE,并且这两种布尔值可以通过逻辑运算符AND、OR和NOT组合在一起。由于上面刚刚讲到的NULL值出现,比较结果可能产生第三个布尔值:UNKNOWN。</p>
</li>
<li>
<p><strong>排序</strong>:</p>
<p>有时需要对查询结果的元组以某种顺序表示。可以基于任何一个属性来排序,并且将其他的属性跟在它之后进行约束。当第一属性值相同时,将第二个属性作为排序的依据,如此类推。为了获得排序的输出结果,在select-from-where语句后加上如下子句用于排序:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ORDER</span> <span class="k">BY</span> <span class="o"><</span><span class="n">list</span> <span class="k">of</span> <span class="n">attributes</span><span class="o">></span>
</code></pre></div> </div>
<p>而且排序的属性即使没有被SELECT也可以使用,默认为升序(ASC,通常省略)排列,属性后面接DESC表示降序。如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span>
<span class="k">FROM</span> <span class="n">R</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">A</span> <span class="o">+</span> <span class="n">B</span> <span class="k">DESC</span><span class="p">;</span>
</code></pre></div> </div>
</li>
</ul>
<h3 id="多关系查询">多关系查询</h3>
<ul>
<li>
<p>积和连接</p>
<p>例:找出 电影Star Wars的制片人名字。如下所示:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">name</span>
<span class="k">FROM</span> <span class="n">Movies</span><span class="p">,</span> <span class="n">MovieExec</span>
<span class="k">WHERE</span> <span class="n">title</span> <span class="o">=</span> <span class="s1">'Star Wars'</span> <span class="k">AND</span> <span class="n">producerC</span><span class="o">#</span> <span class="o">=</span> <span class="n">cert</span><span class="o">#</span><span class="p">;</span>
</code></pre></div> </div>
<p><strong>Tips1</strong>:这里的select是把Movies和MovieExec做了笛卡尔积,所以如果不加<code class="language-plaintext highlighter-rouge">producerC# = cert#</code>会导致结果不正确,其实用连接(join)会更好。</p>
<p><strong>Tips2</strong>:Mysql中producerC#、cert#要用`符号包起来,而且mysql中不区分查询字符串的大小写,如果要区分,在定义表结构时后面加binnary限制。</p>
</li>
<li>
<p>消除属性歧义</p>
<p>有时当查询涉及几个关系的时候,关系中可能会有两个或两个以上的属性具有相同的名字。如果是这样,就需要用明确的方式指定这些相同名字的属性是如何被使用的。SQL通过在属性前面加上关系名和一个点来解决这个问题。如R.A表示关系R的属性A。</p>
</li>
<li>
<p>元组变量</p>
<p>有时候可能会针对同一个关系内的属性进行查询比较,这时候可以为FROM后面出现的关系名定义一个别名,就是原关系后面跟一个AS(可选)和别名。<br />
例:找出相同地址的影星。</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span>
<span class="n">star1</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">star2</span><span class="p">.</span><span class="n">name</span>
<span class="k">FROM</span>
<span class="n">MovieStar</span> <span class="n">star1</span><span class="p">,</span>
<span class="n">MovieStar</span> <span class="n">star2</span>
<span class="k">WHERE</span>
<span class="n">star1</span><span class="p">.</span><span class="n">address</span> <span class="o">=</span> <span class="n">star2</span><span class="p">.</span><span class="n">address</span> <span class="k">and</span> <span class="n">star1</span><span class="p">.</span><span class="n">name</span> <span class="o"><</span> <span class="n">star2</span><span class="p">.</span><span class="n">name</span><span class="p">;</span>
</code></pre></div> </div>
</li>
<li>
<p>交</p>
<p>例:找出那些既是女影星又同时是具有超过$10000000资产的制片人的名字</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">SELECT</span> <span class="n">name</span><span class="p">,</span> <span class="n">address</span>
<span class="k">FROM</span> <span class="n">Mov</span> <span class="n">ieStar</span>
<span class="k">WHERE</span> <span class="n">gender</span> <span class="o">=</span> <span class="s1">'F'</span><span class="p">)</span>
<span class="k">INTERSECT</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="n">name</span><span class="p">,</span> <span class="n">address</span>
<span class="k">FROM</span> <span class="n">MovieExec</span>
<span class="k">WHERE</span> <span class="n">netWorth</span> <span class="o">></span> <span class="mi">10000000</span><span class="p">)</span> <span class="p">;</span>
</code></pre></div> </div>
</li>
<li>
<p>差</p>
<p>例:找出不是电影公司制片人的影星的名字和地址</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">SELECT</span> <span class="n">name</span><span class="p">,</span> <span class="n">address</span> <span class="k">FROM</span> <span class="n">MovieStar</span><span class="p">)</span>
<span class="k">EXCEPT</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="n">name</span> <span class="p">,</span> <span class="n">address</span> <span class="k">FROM</span> <span class="n">MovieExec</span><span class="p">)</span>
</code></pre></div> </div>
</li>
<li>
<p>并</p>
<p>例:找出所有出现在Movies或StarsIn关系中的电影的名字和年份。</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span> <span class="nb">year</span> <span class="k">FROM</span> <span class="n">Movie</span><span class="p">)</span>
<span class="k">UNION</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="n">movieTit1e</span> <span class="k">AS</span> <span class="n">title</span><span class="p">,</span> <span class="n">movieYear</span> <span class="k">AS</span> <span class="nb">year</span> <span class="k">FROM</span> <span class="n">StarsIn</span><span class="p">);</span>
</code></pre></div> </div>
</li>
</ul>
<h3 id="子查询">子查询</h3>
<p>在SQL中,一个查询可以通过不同的方式被用来计算另一个查询。当某个查询是另一个查询的一部分时,称之为子查询(subquery)。 子查询还可以拥有下一-级的子查询。如此递推可以随需要拥有多级子查询。前面已经看到过使用子查询的例子,通过连接两个子查询形成一个新的查询可以完成关系的并、交和差。还有一些使用子查询的其他方式:</p>
<ol>
<li>子查询可以返回单个常量,这个常量能在WHERE子句中和另一个常量进行比较。</li>
<li>子查询能返回关系,该关系可以在WHERE子句中以不同的方式使用。</li>
<li>像许多存储的关系一样,子查询形成的关系能出现在FROM子句中,并且后面紧跟该关系的元组变量。</li>
</ol>
<h4 id="标量子查询">标量子查询</h4>
<p>一个能成为元组字段值的原子值称为标量(scalar)。其实就是查询出来只有一个字段的关系可以看做是一个属性变量。例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">SELECT</span>
<span class="n">name</span>
<span class="k">FROM</span> <span class="n">MovieExec</span>
<span class="k">WHERE</span> <span class="n">cert</span><span class="o">#</span> <span class="o">=</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="n">producerC</span><span class="o">#</span>
<span class="k">FROM</span> <span class="n">Movies</span>
<span class="k">WHERE</span> <span class="n">title</span> <span class="o">=</span> <span class="s1">'Star Wars'</span><span class="p">);</span>
</code></pre></div></div>
<h4 id="条件表达式子查询">条件表达式子查询</h4>
<ul>
<li>
<p>关系的条件表达式</p>
<p>有许多SQL运算符可以作用在关系R上并产生布尔值结果,但关系R必须被表示为子查询。一些运算符,如EXISTS、IN、ALL和ANY,在包含标量值s的简单形式中将首先被解释。下面给出解释:</p>
<ol>
<li>EXISTS R是一个条件,表示当且仅当R非空时为真。</li>
<li>s IN R为真,当且仅当s等于R中的某-个值。类似地,s NOT INR为真,当且仅当s不等于R中的任何一个值。</li>
<li>s> ALL R为真,当且仅当s大于一元关系R中的任何一个值。</li>
<li>s> ANY R为真,当且仅当s至少大于一元关系R中的某个值。</li>
</ol>
</li>
<li>
<p>元组的条件表达式</p>
<p>元组在SQL中通过括号括起来的标量值列表来表达。如果一个元组t和关系R的元组有相同的组成分量个数,那么使用上面条件表达式运算对t和R进行比较是有意义的。例如r IN R或t<>ANY R。(注意,当对一个元组和关系R的成员进行比较时,必须按照关系属性的假定标准顺序来比较各字段值。)</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">name</span>
<span class="k">FROM</span> <span class="n">MovieExec</span>
<span class="k">WHERE</span> <span class="n">cert</span><span class="o">#</span> <span class="k">IN</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="n">producerC</span><span class="o">#</span>
<span class="n">FRDM</span> <span class="n">Movies</span>
<span class="k">WHERE</span> <span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="nb">year</span><span class="p">)</span> <span class="k">IN</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="n">movieTitle</span><span class="p">,</span> <span class="n">movieYear</span>
<span class="k">FROM</span> <span class="n">StarsIn</span>
<span class="k">WHERE</span> <span class="n">starName</span> <span class="o">=</span> <span class="s1">'Harrison Ford'</span> <span class="p">,</span>
<span class="p">);</span>
</code></pre></div> </div>
</li>
<li>
<p>关联子查询</p>
<p>最简单的子查询只需计算一次,它返回的结果用于高层查询。复杂的嵌套子查询要求一个子查询计算多次,每次赋给查询中的某项来自子查询外部的某个元组变量的值。这种类型的子查询叫做关联(correlated)子查查。例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">title</span>
<span class="k">FROM</span> <span class="n">Movies</span> <span class="k">Old</span>
<span class="k">WHERE</span> <span class="nb">year</span> <span class="o"><</span> <span class="k">ANY</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="nb">year</span>
<span class="k">FROM</span> <span class="n">Movies</span>
<span class="k">WHERE</span> <span class="n">title</span> <span class="o">=</span> <span class="k">Old</span><span class="p">.</span><span class="n">title</span>
<span class="p">);</span>
</code></pre></div> </div>
<p>其实就是外层的关系在子查询中还使用了。</p>
</li>
<li>
<p>FROM子句中子查询</p>
<p>子查询的另一个作用是在FROM子句中当关系使用。在FROM列表中,除了使用一个存储关系以外,还可以使用括起来的子查询。由于这个子查询的结果没有名字,必须给它取一个元组变量别名。然后就可以像引用FROM子句中关系的元组一样引用子查询结果中的元组。例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">name</span>
<span class="k">FROM</span> <span class="n">MovieExec</span><span class="p">,</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">producerC</span><span class="o">#</span>
<span class="k">FROM</span> <span class="n">Movies</span><span class="p">,</span> <span class="n">StarBIn</span>
<span class="k">WHERE</span> <span class="n">title</span> <span class="o">=</span> <span class="n">movieTitle</span> <span class="k">AND</span>
<span class="nb">year</span> <span class="o">=</span> <span class="n">movieYear</span> <span class="k">AND</span>
<span class="n">starName</span> <span class="o">=</span> <span class="s1">'Harrison Ford'</span>
<span class="p">)</span> <span class="n">Prod</span>
<span class="k">WHERE</span> <span class="n">cert</span><span class="o">#</span> <span class="o">=</span> <span class="n">Prod</span><span class="p">.</span> <span class="n">producerC</span><span class="o">#</span> <span class="p">;</span>
</code></pre></div> </div>
</li>
</ul>
<h4 id="连接查询">连接查询</h4>
<p>可以通过将许多不同的连接运算符作用在两个关系上创建新的关系。这些不同的运算包括积、自然连接、θ连接和外连接,结果本身可以作为一个查询。另外,由于这些表达式产生的是关系,所以这些表达式可以在select-from-where表达式中的FROM子句中用作子查询。</p>
<ul>
<li>
<p>交叉连接(笛卡尔积、积)</p>
<p>例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">Movies</span> <span class="k">CROSS</span> <span class="k">JOIN</span> <span class="n">StarsIn</span><span class="p">;</span>
</code></pre></div> </div>
<p><strong>注意</strong> 其实这个和<code class="language-plaintext highlighter-rouge">SELECT * FROM Movies, StarsIn</code>查询的结果是一样的。</p>
<p>但是一般笛卡尔积查出来的没啥用,还是要加上<code class="language-plaintext highlighter-rouge">ON</code>对字段进行约束。比如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">Movies</span> <span class="k">CROSS</span> <span class="k">JOIN</span> <span class="n">MovieExec</span> <span class="k">ON</span> <span class="nv">`producerC#`</span> <span class="o">=</span> <span class="nv">`cert#`</span><span class="p">;</span>
</code></pre></div> </div>
</li>
<li>
<p>自然连接</p>
<p>自然连接是对两个关系中具有相同名字并且其值相同的属性作连接,除此之外再没有其他的条件。并且两个等值的属性只投影一个。使用<code class="language-plaintext highlighter-rouge">NATURAL JOIN</code>关键字,如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">MovieStar</span> <span class="k">NATURAL</span> <span class="k">JOIN</span> <span class="n">MovieExec</span><span class="p">;</span>
</code></pre></div> </div>
<p><strong>Tips</strong> 自然连接会自动匹配两个关系的属性进行连接,匹配不上的会丢弃,这个前面理论部分也有说到。</p>
</li>
<li>
<p>内连接(补充)</p>
<p>内连接使用<code class="language-plaintext highlighter-rouge">INNER JOIN</code>关键字,和自然连接相似,但是自然连接中自动匹配的属性在内连接中要用<code class="language-plaintext highlighter-rouge">ON</code>显式定义,并且同名属性会出现多次。如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span>
<span class="k">FROM</span> <span class="n">MovieStar</span>
<span class="k">INNER</span> <span class="k">JOIN</span> <span class="n">MovieExec</span>
<span class="k">ON</span> <span class="n">MovieStar</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">MovieExec</span><span class="p">.</span><span class="n">name</span> <span class="k">AND</span> <span class="n">MovieStar</span><span class="p">.</span><span class="n">address</span> <span class="o">=</span><span class="n">MovieExec</span><span class="p">.</span><span class="n">address</span> <span class="p">;</span>
</code></pre></div> </div>
<p><strong>注意</strong>: mysql中join等同于inner join, left join 等同于left outer join,right join 等同于right outer join,full join等同于交叉连接。</p>
</li>
<li>
<p>外连接</p>
<p>外连接分为<code class="language-plaintext highlighter-rouge">完全外连接</code>,<code class="language-plaintext highlighter-rouge">左外连接</code>,<code class="language-plaintext highlighter-rouge">右外连接</code>三种。前面理论部分也说了,就是考虑悬浮元组的问题。如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">//</span><span class="err">左外连接</span>
<span class="k">SELECT</span> <span class="o">*</span>
<span class="k">FROM</span> <span class="n">MovieStar</span>
<span class="k">LEFT</span> <span class="k">JOIN</span> <span class="n">MovieExec</span>
<span class="k">ON</span> <span class="n">MovieStar</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">MovieExec</span><span class="p">.</span><span class="n">name</span> <span class="k">AND</span> <span class="n">MovieStar</span><span class="p">.</span><span class="n">address</span> <span class="o">=</span><span class="n">MovieExec</span><span class="p">.</span><span class="n">address</span> <span class="p">;</span>
</code></pre></div> </div>
<p><strong>注意</strong>:mysql暂不支持完全外连接。</p>
</li>
</ul>
<p><strong>注意</strong>:内连接不写连接条件会出现和笛卡尔积一样的情况,外连接不写连接条件则会报错。<br />
<strong>注意</strong>:where是在连表操作完成后再根据where条件进行数据过滤,效率低不建议使用。</p>
<h3 id="全关系操作">全关系操作</h3>
<p>首先,sql中把关系看作包而不是集合,所以一个元组可以在关系中多次出现。</p>
<h4 id="消除重复">消除重复</h4>
<ul>
<li>
<p>通过使用DISTINCT关键字消除重复。</p>
<p>例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">DISTINCT</span> <span class="n">address</span>
<span class="k">FROM</span> <span class="n">MovieExec</span><span class="p">;</span>
</code></pre></div> </div>
</li>
<li>
<p>交并差中的重复使用ALL关键字保留。</p>
<p>因为交并差运算会自动转换成集合进行操作,所以重复自动消除,如果要保留则需要使用<code class="language-plaintext highlighter-rouge">ALL</code>关键字,如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span> <span class="nb">year</span> <span class="k">FROM</span> <span class="n">Movie</span><span class="p">)</span>
<span class="k">UNION</span> <span class="k">ALL</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="n">movieTit1e</span> <span class="k">AS</span> <span class="n">title</span><span class="p">,</span> <span class="n">movieYear</span> <span class="k">AS</span> <span class="nb">year</span> <span class="k">FROM</span> <span class="n">StarsIn</span><span class="p">);</span>
</code></pre></div> </div>
</li>
</ul>
<p><strong>注意</strong>:或许有人想在每个SELECT后面放上一个DISTINCT消除重复,这样做在理论上似乎不费事,但实际上从关系中消除重复的代价非常昂贵,关系必须排序或者分组才能保证相同的元组紧挨在一起,也只有按该算法分组后才能决定某个元组是否可以去掉,为消除重复对元组进行排序的时间通常比执行查询的时间更长,所以如果想要查询运行得快就要谨慎地使用消除重复。</p>
<h4 id="聚集和分组">聚集和分组</h4>
<p>这里做个简单地回顾,聚集操作符有五个,<code class="language-plaintext highlighter-rouge">SUM、AVG、MIN、MAX、COUNT</code>。如果忘记了可以往前面翻。分组则使用<code class="language-plaintext highlighter-rouge">GROUP BY</code>。聚集一般和分组结合起来使用的频率比较高。</p>
<ul>
<li>
<p>聚集</p>
<p>例如:找出所有电影制片人资产的平均值。</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">AVG</span><span class="p">(</span><span class="n">netWorth</span><span class="p">)</span>
<span class="k">FROM</span> <span class="n">MovieExec</span><span class="p">;</span>
</code></pre></div> </div>
<p>例如:统计明星的数量。</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="k">DISTINCT</span> <span class="n">starName</span><span class="p">)</span>
<span class="k">FROM</span> <span class="n">StarsIn</span><span class="p">;</span>
</code></pre></div> </div>
</li>
<li>
<p>分组</p>
<p>例如:找出电影公司制作电影的总长度。</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">studioName</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="k">length</span><span class="p">)</span>
<span class="k">FROM</span> <span class="n">Movies</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">studioName</span>
</code></pre></div> </div>
</li>
<li>
<p>空值</p>
<ol>
<li>当元组含有空值时,要记住下面几条规则:
空值在任何聚集操作中都被忽视。它既不对求和、取平均和计数作贡献,也不能是某列的最大值或最小值。例如,COUNT(* )是某个关系中所有的元组数目之和,但是COUNT(A)却是A属性非空的元组个数之和。</li>
<li>另一方面,在构成分组时,NULL值被作为一般的值对待。即分组中的一个或多个分组属性可以被赋予NULL值。</li>
<li>除了计数之外,对空包执行的聚集操作,结果均为NULL。空包的计数结果为0。</li>
</ol>
</li>
</ul>
<h4 id="having子句">HAVING子句</h4>
<p>如果需要对基于某些分组聚集的性质选择分组,可以在GROUPBY子句后面加上一个HAVING子句,后者由保留字HAVING后跟一个分组的条件组成。例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">name</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="k">length</span><span class="p">)</span>
<span class="k">FROM</span> <span class="n">MovieExec</span><span class="p">,</span> <span class="n">Movies</span>
<span class="k">WHERE</span> <span class="n">producerC</span><span class="o">#</span> <span class="o">=</span> <span class="n">cert</span><span class="o">#</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">name</span>
<span class="k">HAVING</span> <span class="n">NIN</span><span class="p">(</span><span class="nb">year</span><span class="p">)</span> <span class="o"><</span> <span class="mi">1930</span><span class="p">;</span>
</code></pre></div></div>
<p><strong>注意</strong>:HAVING子句中的聚集只应用到正在检测的分组上。所有FROM子句中关系的属性都可以在HAVING子句中用聚集运算,但是只有出现在GROUPBY子句中的属性,才可能以不聚集的方式出现在HAVING子句中。</p>
<h3 id="数据库更新">数据库更新</h3>
<p> 数据库因为是存储数据后便于读取使用,所以一般还是读操作比较多,因此查询操作也是五花八门,但是更新操作就很常规了。插入、删除、修改都称之为更新操作。</p>
<h4 id="插入">插入</h4>
<p>基本形式:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">R</span><span class="p">(</span><span class="n">A1</span><span class="p">,...,</span><span class="n">An</span><span class="p">)</span> <span class="k">VALUES</span><span class="p">(</span><span class="n">V1</span><span class="p">,...,</span><span class="n">Vn</span><span class="p">);</span>
</code></pre></div></div>
<p>当关系所有属性值都给出时,属性列可以省略,如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">StarsIn</span>
<span class="k">VALUES</span><span class="p">(</span><span class="s1">'The Maltese Falcon'</span><span class="p">,</span> <span class="mi">1942</span><span class="p">,</span> <span class="s1">'Sydney GreenStreet'</span><span class="p">);</span>
</code></pre></div></div>
<h4 id="删除">删除</h4>
<p>基本形式:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DELETE</span> <span class="k">FROM</span> <span class="n">R</span> <span class="k">WHERE</span> <span class="o"><</span><span class="err">条件</span><span class="o">></span><span class="p">;</span>
</code></pre></div></div>
<p>例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DELETE</span> <span class="k">FROM</span> <span class="n">MovieExec</span>
<span class="k">WHERE</span> <span class="n">netWorth</span> <span class="o"><</span> <span class="mi">10000000</span><span class="p">;</span>
</code></pre></div></div>
<h4 id="修改">修改</h4>
<p>基本形式:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">UPDATE</span> <span class="n">R</span> <span class="k">SET</span> <span class="o"><</span><span class="err">新值</span><span class="o">></span> <span class="k">WHERE</span> <span class="o"><</span><span class="err">条件</span><span class="o">></span><span class="p">;</span>
</code></pre></div></div>
<p>例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">UPDATE</span> <span class="n">MovieExec</span>
<span class="k">SET</span> <span class="n">name</span> <span class="o">=</span> <span class="err">’</span><span class="n">Pres</span><span class="p">.</span><span class="err">‘</span> <span class="o">||</span> <span class="n">name</span>
<span class="k">WHERE</span> <span class="n">cert</span> <span class="k">IN</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">presC</span> <span class="k">FROM</span> <span class="n">STUDIO</span><span class="p">);</span>
</code></pre></div></div>
<h4 id="数据库关系更新">数据库关系更新</h4>
<ul>
<li>删除数据库</li>
</ul>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">DATABASE</span> <span class="k">schema</span><span class="p">;</span>
</code></pre></div></div>
<ul>
<li>删除表</li>
</ul>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">TABLE</span> <span class="n">R</span><span class="p">;</span>
</code></pre></div></div>
<ul>
<li>修改表</li>
</ul>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">R</span> <span class="n">xxx</span><span class="p">;</span>
<span class="o">#</span> <span class="n">xxx</span><span class="err">可以是添加删除属性、添加删除约束等情况。</span>
</code></pre></div></div>
<h4 id="修改-1">修改</h4>
<h3 id="事务">事务</h3>
<p> 还记得前面第一章节了解的事务的概念吗,ACID能不能记起来是什么意思呢?</p>
<h4 id="可串行化">可串行化</h4>
<p> 有时候我们的数据库在1秒钟之内可能执行很多次操作,但是对于这些操作有些约束,有的操作必须在其他操作之前或之后(比如,新增一个用户并为他增加一个联系表信息,但是新增用户和新增联系表之间如果进来了一个用户,这时可能会导致查询用户联系信息失败。)这时我们需要保证可串行化,一种常见的操作是加锁。</p>
<h4 id="sql中的事务">SQL中的事务</h4>
<p> 当使用基本SQL界面工具时,每条语句自身就是一个事务。SQL允许程序员将几条语句组成一个事务。</p>
<ul>
<li>SQL命令START TRANSACTION可用来标记事务的开始。</li>
<li>SQL语句COMMIT使得事务成功结束。由这条SQL语句或自当前事务开始以来的语句引起的任何对数据库的修改都被持久地建立在数据库中,即它们被提交了(committed)。在COMMIT语句执行之前,改变是试探性的,对其他事务可不可见均有可能。</li>
<li>SQL语句ROLLBACK使得事务夭折(abort) 或不成功结束。任何由该事务的SQL语句所引起的修改都被撤销,即它们被回滚(rolled back),所以它们不会持久地出现在数据库中。</li>
</ul>
<p><strong>注意</strong> 可以使用<code class="language-plaintext highlighter-rouge">SET TRANSACTION READ ONLY</code>表示事务只读,这样数据库可以利用这个特性与其他事务并发执行。(不过我感觉没啥卵用啊,READ不READ、WRITE不WIRTE在SQL语句中不就能体现出来了吗。)</p>
<h4 id="脏数据">脏数据</h4>
<p> 脏数据(Dirtydata)是表示还没有提交的事务所写的数据的通用术语。脏读(dirtyread)
是对脏数据的读取。读脏数据的风险是写数据的事务可能最终天折。如果这样,那么脏数据
将从数据库中移走,就像这个数据不曾存在过。如果某个别的事务读取了这个脏数据,那么
该事务可能提交或采取别的手段反映它对脏数据的了解。
脏读有时是要紧的,有时是无关紧要的。当它非常无关紧要时,可以冒险偶尔脏读-一次,
从而避免:</p>
<ol>
<li>DBMS用来防止脏读所做的耗时的工作。</li>
<li>为了等到不可能出现脏读而造成的并发性的损失。</li>
</ol>
<p>合理的使用事务隔离层次,可以避免错误的脏读。</p>
<h4 id="隔离层次">隔离层次</h4>
<p> 事务的隔离层次只影响该事务可以看到的那些数据,不影响其他事务所看到的数据。作为佐证,如果事务T正在串行化层次运行,那么T的执行必须看起来好像所有其他事务要么完全在T之前运行,要么完全在T之后运行。但是,如果一些事务正运行在其他的隔离层次上,那么它们可以在T写数据时看到T所写的数据。如果它们运行在读未提交隔离层次,它们可以看到来自T的脏数据,且T夭折。</p>
<p>SQL一共提供了四种隔离层次(isolation level):可串行化、读未提交(允许脏读)、读提交、可重复读。</p>
<ul>
<li>
<p>可串行化:</p>
<p>语句:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SET</span> <span class="n">TRANSACTION</span> <span class="k">ISOLATION</span> <span class="k">LEVEL</span> <span class="k">SERIALIZABLE</span><span class="p">;</span>
</code></pre></div> </div>
<p>默认情况,不必显式指定,每次读都需要获得表级共享锁,读写相互都会阻塞。</p>
</li>
<li>
<p>读未提交:</p>
<p>语句:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SET</span> <span class="n">TRANSACTION</span> <span class="k">READ</span> <span class="k">WRITE</span> <span class="k">ISOLATION</span> <span class="k">LEVEL</span> <span class="k">READ</span> <span class="n">UNCDMMITTED</span> <span class="p">;</span>
</code></pre></div> </div>
<p>这种情况下允许脏读</p>
</li>
<li>
<p>读提交(不重复读)</p>
<p>语句:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SET</span> <span class="n">TRANSACTION</span> <span class="k">ISOLATION</span> <span class="k">LEVEL</span> <span class="k">READ</span> <span class="k">COMMITTED</span> <span class="p">;</span>
</code></pre></div> </div>
<p>读提交隔离层次禁止读取脏数据(未提交数据)。但是它允许一个在该隔离层次执行的事务多次发出同一个查询并得到不同的答案,只要答案反映了已提交事务写入的数据。</p>
</li>
<li>
<p>可重复读。</p>
<p>语句:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SET</span> <span class="n">TRANSACTION</span> <span class="k">ISOLATION</span> <span class="k">LEVEL</span> <span class="k">REPEATABLE</span> <span class="k">READ</span> <span class="p">;</span>
</code></pre></div> </div>
<p>读完之后加锁,保证下次读取的数据还是同样的元组,其它事务无法<code class="language-plaintext highlighter-rouge">修改</code>这些数据,不过也有可能产生<code class="language-plaintext highlighter-rouge">幻读</code>,因为有可能事务会产生数据的插入操作。</p>
</li>
</ul>
<p>区别:</p>
<table>
<thead>
<tr>
<th>隔离级别</th>
<th>脏读</th>
<th>不可重复读</th>
<th>幻读</th>
</tr>
</thead>
<tbody>
<tr>
<td>未提交读(Read uncommitted)</td>
<td>可能</td>
<td>可能</td>
<td>可能</td>
</tr>
<tr>
<td>已提交读(Read committed)</td>
<td>不可能</td>
<td>可能</td>
<td>可能</td>
</tr>
<tr>
<td>可重复读(Repeatable read)</td>
<td>不可能</td>
<td>不可能</td>
<td>可能</td>
</tr>
<tr>
<td>可串行化(Serializable )</td>
<td>不可能</td>
<td>不可能</td>
<td>不可能</td>
</tr>
</tbody>
</table>
<h2 id="第七章-约束与触发器">第七章 约束与触发器</h2>
<p> 虽然数据的插入由用户决定,但是人总是粗心的,有时候可能会造成插入的数据不符合完整性约束,或者需要在某种操作进行时需要对数据进行检查,约束和触发器应运而生。为了保证数据的完整性约束,SQL提供了各种技术把完整性约束作为数据库模式的一部分。本章节就是介绍如何利用约束与触发器来保证数据完整性。</p>
<h3 id="名词解释-2">名词解释</h3>
<ul>
<li><strong>引用完整性约柬(Referential-Integrity Constraint)</strong>:可以声明出现在某个属性或一组属
性中的值,必须也出现在同一个关系或另一个关系的某个元组相应的属性(组)中。为此,在关系模式中使用REFERENCES或FOREIGN KEY声明。</li>
<li><strong>基于属性的检查约束(Attribute- Based Check Constraint)</strong>: 关系模式属性声明的后面加
保留字CHECK和要检查的条件,可以实现对属性值的约束。</li>
<li><strong>基于元组的检查约束(Tuple-Based Check Constraint)</strong>: 通过在关系本身的声明中加
CHECK保留字和要检查的条件,可以实现对关系元组的约束。</li>
<li><strong>修改约束(Modifying Constraint)</strong>:用ALTER语 句为适当的表添加或删除基于元组的检查约束。</li>
<li><strong>断言(Assertion)</strong>: 可以声明断言为数据库模式的元素。该声明给出一个要检查的条件。该条件可以涉及-一个或多个数据库模式关系,还可以将整个关系作为一个整体(例如,用聚集),也可以只对单个的元组。</li>
<li><strong>激活裣查(Invoking the Check)</strong>: 断言涉及的关系被改变时,断言声明的条件被检查。基于属性和基于元组的检查仅仅当属性或关系用插入或修改操作改变时被检查。因此,这些约束有子查询时被违反。</li>
<li><strong>触发器(Trigger)</strong>: SQL标准包括触发器,它指明唤醒该触发的特定事件(例如,对某
个关系的插人、删除或修改)。一且触发器被唤醒,触发的条件便被检查。如果条件是
真,则指明的动作序列(SQL语句, 如查询和数据库更新)将被执行。</li>
</ul>
<h3 id="键和外键">键和外键</h3>
<h4 id="键">键</h4>
<p> 键我们应该很熟悉了,前面也大量介绍过关于键的知识。数据库常用<code class="language-plaintext highlighter-rouge">PRIMARY KEY</code>和<code class="language-plaintext highlighter-rouge">UNIQUE</code>来定义一个或一组属性为一个关系的键。</p>
<h4 id="外键">外键</h4>
<p> 外键约束是一个断言,它要求某些属性的值必须有意义。比如对于每部影片,其制片人的“证书号”也是Movi eExec关系中某制片人的证书号。<br />
在SQL中可以将关系的一个属性或属性组声明为外键(foreign key),该外键引用另一个
关系(也可以是同一个关系)的属性(组)。外键声明隐含着如下两层意思:</p>
<ol>
<li>被引用的另一个关系的属性在它所在的关系中,必须被声明为UNIQUE或PRIMARY KEY,否则就不能做外键声明。</li>
<li>在第一个关系中出现的外键值,也必须在被引用关系的某个元组的属性中出现。</li>
</ol>
<h3 id="约束">约束</h3>
<h4 id="外键约束">外键约束</h4>
<p>外键约束有两种方法:</p>
<ul>
<li>如果外键是单个属性,则可以在此属性的名字和类型之后,声明其”引用”某个表的
某个属性(被引用的属性必须有主键或唯一性声明)。声明的格式如下:</li>
</ul>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">REFERENCES</span> <span class="o"><</span><span class="err">表名</span><span class="o">></span> <span class="p">(</span><span class="o"><</span><span class="err">属性名</span><span class="o">></span><span class="p">)</span>
<span class="o">#</span> <span class="err">如</span>
<span class="n">presC</span> <span class="nb">INT</span> <span class="k">REFERENCES</span> <span class="n">MovieExec</span><span class="p">(</span><span class="n">cert</span><span class="p">)</span>
</code></pre></div></div>
<ul>
<li>在CREATE TABLE语句的属性列表上追加一个或多个声明,来说明一组属性是一个外键。然后给出外键引用的表和属性(这些属性必须是键)。声明的格式为:</li>
</ul>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="err">(</span><span class="o"><</span><span class="err">属性</span> <span class="err">名列表</span><span class="o">></span><span class="p">)</span> <span class="n">REFERENCE</span> <span class="o"><</span><span class="err">表名</span><span class="o">></span> <span class="p">(</span><span class="o"><</span><span class="err">属性名列表</span><span class="o">></span><span class="err">)</span>
<span class="o">#</span> <span class="err">如</span>
<span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">presC</span><span class="p">)</span> <span class="k">REFERENCES</span> <span class="n">MovieExec</span><span class="p">(</span><span class="n">cert</span><span class="p">)</span>
</code></pre></div></div>
<h4 id="引用完整性">引用完整性</h4>
<p> 对于定义了外键约束的关系上,对两边进行更新(注:本书中更新指的是增加、删除、修改三种操作,这个在上一章节介绍数据库的时候有说到)操作时可能会违反完整性约束,因此可以在更新操作时定义维护完整性约束的操作。一般有三种:</p>
<ol>
<li>缺省原则(The Default Policy): 拒绝违法更新。SQL有缺省原则,即拒绝任何违反引用完整性约束的更新。<br />
2.级联原则(The Cascade Policy): 在该原则下,被引用属性(组)的改变也被应用到外键上。例如,在级联原则下,当对电影公司经理删除MovieExec元组时,为了维护引用完整性,系统将从Studio中删除引用元组。如果对于某电影制片人将cert#的值从c1修改为C2,同时有某Studio元组的presC#值是c,则系统也把该presC#值修改为c2。<br />
3.置空值原则(The Set-Null Policy): 这里,当在被引用的关系上的更新影响外键值时,后者被改为空值(NULL)。 例如,如果从MovieExec中删除一个电影公司经理的元组,则系统会把该电影公司的presC#值改为空值。如果修改MovieExec中经理的证书号,则还是在Studio+中把presC#置为空值。</li>
</ol>
<p> 这些选项可独立地选择删除和修改,并且它们同外键一起声明。声明的方法是在0N DELETE 或ON UPDATE后面加上SET NULL或CASCADE选项 。例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">Studio</span> <span class="p">(</span>
<span class="n">name</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">addrees</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">265</span><span class="p">)</span> <span class="p">,</span>
<span class="n">preaC</span> <span class="nb">INT</span> <span class="k">REFERENCES</span> <span class="n">MovieExec</span><span class="p">(</span><span class="n">cert</span><span class="p">)</span>
<span class="k">ON</span> <span class="k">DELETE</span> <span class="k">SET</span> <span class="k">NULL</span>
<span class="k">ON</span> <span class="k">UPDATE</span> <span class="k">CASCADE</span>
<span class="p">);</span>
</code></pre></div></div>
<h4 id="延迟约束检查">延迟约束检查</h4>
<p> 有时候会出现循环引用的情况,导致A插入时需要B存在,B插入时需要A存在,这时候由于约束的立即检查特性,导致数据无法正常插入,因此需要使用延迟约束检查,即把检查推迟到事务提交之前。使用<code class="language-plaintext highlighter-rouge">DEFERRABLE</code>(或者<code class="language-plaintext highlighter-rouge">NOT DEFERRABLE</code>,是默认值)选项进行约束,后面接<code class="language-plaintext highlighter-rouge">INITIALLY DEFERRED</code>或者<code class="language-plaintext highlighter-rouge">INITALLY IMMEDIATE</code>选项。前者推迟到事务提交前检查,后者每个语句后面都检查。如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">Studio</span> <span class="p">(</span>
<span class="n">name</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">addre8s</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="p">,</span>
<span class="n">presC</span> <span class="nb">INT</span> <span class="k">UNIQUE</span>
<span class="k">REFERENCES</span> <span class="n">MovieExec</span><span class="p">(</span><span class="n">cert</span><span class="p">)</span>
<span class="k">DEFERRABLE</span> <span class="k">INITIALLY</span> <span class="k">DEFERRED</span>
<span class="p">);</span>
</code></pre></div></div>
<p><strong>思考</strong>:书上<code class="language-plaintext highlighter-rouge">NOT DEFERRABLE</code>和<code class="language-plaintext highlighter-rouge">INITALLY IMMEDIATE</code>都说是语句后立即检查,他们有何区别,组合起来有什么效果?这里我偷个懒,大侠路过的话试一试留言告诉我吧。</p>
<h4 id="非空值约束">非空值约束</h4>
<p>直接在字段后面加<code class="language-plaintext highlighter-rouge">NOT NULL</code>,表示不能为空值</p>
<h4 id="check约束">check约束</h4>
<ul>
<li>基于属性</li>
</ul>
<p>基于属性的check约束是值上的简单约束,如合法值得枚举或者算术不等式。同时,也可以是一个子查询的检查。例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">presC</span> <span class="nb">INT</span> <span class="k">REFERENCES</span> <span class="n">MovieExec</span><span class="p">(</span><span class="n">cert</span><span class="p">)</span> <span class="k">CHECK</span> <span class="p">(</span><span class="n">presC</span> <span class="o">>=</span> <span class="mi">100000</span><span class="p">),</span>
<span class="n">gender</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">CHECK</span> <span class="p">(</span><span class="n">gender</span> <span class="k">IN</span> <span class="p">(</span><span class="s1">'F'</span><span class="p">,</span> <span class="s1">'M'</span><span class="p">)),</span>
<span class="n">presC</span> <span class="nb">INT</span> <span class="k">CHECK</span><span class="p">(</span><span class="n">presC</span> <span class="k">IN</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">cert</span> <span class="k">FROM</span> <span class="n">MovieExec</span><span class="p">))</span>
</code></pre></div></div>
<ul>
<li>基于元组</li>
</ul>
<p>有时候需要同时对一个元组进行多项检查,这时可以用到基于元组的check,但是要注意基于元组的检查会比基于属性的检查要频繁,需要谨慎使用。例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">MovieStar</span> <span class="p">(</span>
<span class="n">name</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">address</span> <span class="nb">VARCHAR</span> <span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="p">,</span>
<span class="n">gender</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">,</span>
<span class="n">birthdate</span> <span class="nb">DATE</span><span class="p">,</span>
<span class="k">CHECK</span> <span class="p">(</span><span class="n">gender</span> <span class="o">=</span> <span class="s1">'F'</span> <span class="k">OR</span> <span class="n">name</span> <span class="k">NOT</span> <span class="k">LIKE</span> <span class="s1">'Ms.%'</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<h4 id="修改约束">修改约束</h4>
<p>任何时候都可以添加、修改、删除约束,但是约束必须要有名字。命名方式为在约束前加保留字constraint和约束名字。如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">name</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="k">CONSTRAINT</span> <span class="n">NameIsKey</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">gender</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">CONSTRAINT</span> <span class="n">NoAndor</span> <span class="k">CHECK</span> <span class="p">(</span><span class="n">gender</span> <span class="k">IN</span> <span class="p">(</span><span class="s1">'F'</span><span class="p">,</span> <span class="s1">'M'</span><span class="p">)),</span>
</code></pre></div></div>
<p>修改例子如下:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">MovieStar</span> <span class="k">DROP</span> <span class="k">CONSTRAINT</span> <span class="n">NameIsKey</span><span class="p">;</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">HovieStar</span> <span class="k">DROP</span> <span class="k">CONSTRAINT</span> <span class="n">NoAndro</span><span class="p">;</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">MovieStar</span> <span class="k">ADD</span> <span class="k">CONSTRAINT</span> <span class="n">NameIsKey</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="p">;</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">Movi</span> <span class="n">eStar</span> <span class="k">ADD</span> <span class="k">CONSTRAINT</span> <span class="n">NoAndro</span> <span class="k">CHECK</span> <span class="p">(</span><span class="n">gender</span> <span class="k">IN</span> <span class="p">(</span><span class="s1">'F'</span><span class="p">,</span> <span class="s1">'M'</span><span class="p">));</span>
</code></pre></div></div>
<h3 id="断言">断言</h3>
<p> SQL中主动元素的最强有力的形式与特定的元组或元组的分量并不相关。这些元素称作“触发器”和“断言”,它们是数据库模式的一部分,等同于表。并且:</p>
<ul>
<li>断言是SQL逻辑表达式,并且总是为真。</li>
<li>触发器是与某个事件相关的一系列动作,例如向关系中插入元组。触发器总是当这些事件发生时被执行。</li>
</ul>
<p>基本语法:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#</span> <span class="err">创建</span>
<span class="k">CREATE</span> <span class="k">ASSERTION</span> <span class="o"><</span><span class="err">断言名</span><span class="o">></span> <span class="k">CHECK</span><span class="p">(</span><span class="o"><</span><span class="err">断言条件</span><span class="o">></span><span class="p">);</span>
<span class="o">#</span> <span class="err">删除</span>
<span class="k">DROP</span> <span class="k">ASSERTION</span> <span class="o"><</span><span class="err">断言名</span><span class="o">></span><span class="p">;</span>
</code></pre></div></div>
<p>例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">ASSERTION</span> <span class="n">RichPres</span> <span class="k">CHECK</span>
<span class="p">(</span><span class="k">NOT</span> <span class="k">EXISTS</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="n">Studio</span><span class="p">.</span><span class="n">name</span>
<span class="k">FROM</span> <span class="n">Studio</span><span class="p">,</span> <span class="n">MovieExec</span>
<span class="k">WHERE</span> <span class="n">presC</span> <span class="o">=</span> <span class="n">cert</span> <span class="k">AND</span> <span class="n">netWorth</span> <span class="o"><</span> <span class="mi">10000000</span>
<span class="p">)</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">ASSERTION</span> <span class="n">SunLength</span> <span class="k">CHECK</span> <span class="p">(</span><span class="mi">10000</span> <span class="o">>=</span> <span class="k">ALL</span>
<span class="p">(</span><span class="k">SELECT</span> <span class="k">SUM</span><span class="p">(</span><span class="k">length</span><span class="p">)</span> <span class="n">FRDM</span> <span class="n">Hoviea</span> <span class="n">GRQUP</span> <span class="k">BY</span> <span class="n">studi</span> <span class="n">oName</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>比较:</p>
<table>
<thead>
<tr>
<th>约束类型</th>
<th>声明的位置</th>
<th>动作的时间</th>
<th>确保成立?</th>
</tr>
</thead>
<tbody>
<tr>
<td>断言</td>
<td>数据库模式元素</td>
<td>对任何提及的关系做改变时</td>
<td>是</td>
</tr>
<tr>
<td>基于属性的CHECK</td>
<td>属性</td>
<td>对关系插人元组或属性修改时</td>
<td>如果是子查询,则不能确保</td>
</tr>
<tr>
<td>基于元组的CHECK</td>
<td>关系模式元素</td>
<td>对关系插人元组或属性修改时</td>
<td>如果是子查询,则不能确保</td>
</tr>
</tbody>
</table>
<h3 id="触发器">触发器</h3>
<p> 触发器(trigger) 有时也称作事件~条件-动作规则(event condition-action rule),或者ECA规则。触发器与前面已介绍的几种约束有如下三点不同。</p>
<ol>
<li>仅当数据库程序员声明的事件发生时,触发器被激活。所允许的事件种类通常是对某
个特定关系的插人、删除或修改。很多SQL系统中允许的另一种事件是事务的结束。</li>
<li>当触发器被事件激活时,触发器测试触发的条件(condiction)。 如果条件不成立,则响应该事件的触发器不做任何事情。</li>
<li>如果触发器声明的条件满足,则与该触发器相连的动作(action) 由DBMS执行。动作可以是以某种方式修改事件的结果,甚至可以是撤销事件所在的事务。事实上,动作可以是任何数据库操作序列,包括与触发事件毫无关联的操作。</li>
</ol>
<p>例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="mi">1</span><span class="p">)</span><span class="k">CREATE</span> <span class="k">TRIGGER</span> <span class="n">NetWorthTrigger</span>
<span class="mi">2</span><span class="p">)</span><span class="k">AFTER</span> <span class="k">UPDATE</span> <span class="n">DF</span> <span class="n">netWorth</span> <span class="k">ON</span> <span class="n">MovieExec</span>
<span class="mi">3</span><span class="p">)</span><span class="k">REFERENCING</span>
<span class="mi">4</span><span class="p">)</span> <span class="k">OLD</span> <span class="k">ROW</span> <span class="k">AS</span> <span class="n">OldTuple</span><span class="p">,</span>
<span class="mi">5</span><span class="p">)</span> <span class="k">NEW</span> <span class="k">ROW</span> <span class="k">AS</span> <span class="n">NesTuple</span>
<span class="mi">6</span><span class="p">)</span><span class="k">FOR</span> <span class="k">EACH</span> <span class="k">ROW</span>
<span class="mi">7</span><span class="p">)</span><span class="k">WHEN</span> <span class="p">(</span><span class="n">OldTuple</span><span class="p">.</span><span class="n">netWorth</span> <span class="o">></span> <span class="n">NeuTuple</span><span class="p">.</span><span class="n">netWorth</span><span class="p">)</span>
<span class="mi">8</span><span class="p">)</span> <span class="k">UPDATE</span> <span class="n">MovieExec</span>
<span class="mi">9</span><span class="p">)</span> <span class="k">SET</span> <span class="n">netHorth</span> <span class="o">=</span> <span class="n">oldTuple</span><span class="p">.</span><span class="n">netWorth</span>
<span class="mi">10</span><span class="p">)</span> <span class="k">WHERE</span> <span class="n">cert</span> <span class="o">=</span> <span class="n">NewTuple</span><span class="p">.</span><span class="n">cert</span> <span class="p">;</span>
<span class="n">a</span><span class="p">)</span> <span class="k">CREATE</span> <span class="k">TRIGGER</span><span class="err">语句</span> <span class="p">(</span><span class="err">第</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="err">行</span><span class="p">)</span>
<span class="n">b</span><span class="p">)</span> <span class="err">指出触发事件并告诉触发器是在触发事件之前还是之后使用数据库状态的子句</span><span class="p">(</span><span class="err">第</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="err">行</span><span class="p">)</span><span class="err">。</span>
<span class="k">c</span><span class="p">)</span> <span class="k">REFERENCING</span><span class="err">子句允许触发器的条件和动作引用正被修改的元组</span><span class="p">(</span><span class="err">第</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="err">至</span> <span class="err">第</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="err">行</span><span class="p">)</span><span class="err">。</span>
<span class="err">在更新的情况下,例如本例,该子句允许给在改变之前和之后的元组命名。</span>
<span class="n">d</span><span class="p">)</span> <span class="err">告诉触发器只对每个修改的行执行一次,还是对由</span><span class="k">SQL</span><span class="err">语句作的所有修改执行一次的子句</span><span class="p">(</span><span class="err">第</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span><span class="err">行</span><span class="p">)</span><span class="err">。</span>
<span class="n">e</span><span class="p">)</span> <span class="err">使用保留字</span><span class="k">WHEN</span><span class="err">和逻辑表达式的条件</span><span class="p">(</span><span class="err">第</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span><span class="err">行</span><span class="p">)</span><span class="err">。</span>
<span class="n">f</span><span class="p">)</span> <span class="err">由一个或多个</span><span class="k">SQL</span><span class="err">语句组成的动作</span><span class="p">(</span><span class="err">第</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span><span class="err">至</span> <span class="err">第</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span><span class="err">行</span><span class="p">)</span><span class="err">。</span>
</code></pre></div></div>
<h2 id="第八章-视图与索引">第八章 视图与索引</h2>
<p> 本章首先介绍虛拟视图,虚拟枧图是由其他关系上的查询所定义的一种关系。處拟视图并不在数据库中进行存储,但是可以对其进行查询,就好像它确实被存储在数据库中一样。查询处理器会在执行查询时用视图的定义来替换视图。<br />
视图也可以被物化,即它们从数据库中定期地进行构造并存储。物化视图可以加速查询的执行。其中一种非常重要的“物化视图”类型是索引,索引是一种被存储在数据库中的数据结构,它可以加速对存储的关系中特定元组的访问。本章也将介绍索引,并探讨在存储的表上选择合适索引的原则。</p>
<h3 id="名词解释-3">名词解释</h3>
<ul>
<li><strong>虚拟视图(VirtualView)</strong>:虚拟视图描述了怎样从数据库中存储的表或者其他视图逻辑
地构造出一个新的关系(视图)。视图可以被查询就好像它是存储的关系一样。查询处
理器会修改该查询,将其中的视图替换成定义该视图的基本表。</li>
<li><strong>可更新视图(Updatablc View)</strong>: 有些仅基于一个关系的虚拟视图是可更新的,这意味着
能对该视图作插人、删除以及修改操作,就好像它是存储的表一样。这些操作会转换为
等效的对定义该视图的基本表上的修改。</li>
<li><strong>替换触发器(Instead-Of Trigger)</strong>: SQL允许一类特殊的触发器应用于虚拟视图。当对视
图的更新发生时,替换触发器就会将该更新操作转换为触发器中指定的作用于基本表的
操作。</li>
<li><strong>索引(Index)</strong>: 尽管索引并不是SQL标准的-部分,但是商用SQL系统都允许在属性上
建立索引。当查询或者更新操作涉及建有索引的属性(组)的某个特定值或者一个取值
范围时,索引能够加速该查询的执行。</li>
<li><strong>索引选择(Choosing Index)</strong>:在索引加速查询的同时,它也会减慢数据库的更新,因为
对于要更新的关系来说,它上面的索引也需要被更新。所以索引的选择是一-个很复杂的
问题,是不是要建立索弓|取决于对数据库的操作中执行查询和更新所占比重。</li>
<li><strong>自动索引选择(Automatic Index Selection)</strong>: 有些DBMS会提供I具用于为数据库自动
选择索引。它们考察在数据库上执行的–些典型的查询和更新,并以此评估各种可能的
索引所带来的开销。</li>
<li><strong>物化视图(Materialized View)</strong>:除了把视图当作基本表上的-一个查询外,也可以将它定
义为一个额外存储下来的关系,即物化视图。它的值是基本表的值的-一个函数。</li>
<li><strong>物化视图的维护(MaintainingMaterializedView)</strong>:当基本表改变时,必须对值受到改
变影响的物化视图进行相应的修改。对于很多常见类型的物化视图来说,可以使用增量
式的维护方法,这样可以不需要重新计算整个视图。.</li>
</ul>
<h3 id="虚拟视图">虚拟视图</h3>
<p> <code class="language-plaintext highlighter-rouge">CREATE TABLE</code>定义的关系以物理形式存储在数据库中,是持久的,虚拟视图上的操作也和他差不多,但是虚拟视图从名字可以知道他并不是持久的,而是我们虚拟定义的一种关系,例如把某种查询结果作为视图,他的数据来源于物理形式的关系。</p>
<h4 id="创建视图">创建视图</h4>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">CREATE</span> <span class="k">VIEW</span> <span class="o"><</span><span class="err">视图名</span><span class="o">></span> <span class="k">AS</span> <span class="o"><</span><span class="err">视图定义</span><span class="o">></span><span class="p">;</span>
</code></pre></div></div>
<h4 id="查询视图">查询视图</h4>
<p>视图可以像一个被物理形式存储的表一样来查询。</p>
<h4 id="属性重命名">属性重命名</h4>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">CREATE</span> <span class="k">VIEW</span> <span class="n">MovieProd</span><span class="p">(</span><span class="n">movieTitle</span><span class="p">,</span> <span class="n">prodName</span><span class="p">)</span> <span class="k">AS</span>
<span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span> <span class="n">name</span>
<span class="k">FROM</span> <span class="n">Movies</span><span class="p">,</span> <span class="n">MovieExec</span>
<span class="k">WHERE</span> <span class="n">producerC</span> <span class="o">=</span> <span class="n">cert</span>
</code></pre></div></div>
<h4 id="删除视图">删除视图</h4>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">DROP</span> <span class="k">VIEW</span> <span class="n">MovieProd</span><span class="p">;</span>
</code></pre></div></div>
<h4 id="可更新视图">可更新视图</h4>
<p>视图也可以像表一样更新,但是为了保证基本数据关系约束,有很多规则限制:</p>
<ol>
<li>WHERE子句在子查询中不能使用关系R.</li>
<li>FROM语句只能包含一个关系R,不能再有其他关系。</li>
<li>SELECT语句中的属性列表必须包括足够多的属性,以保证对该视图进行元组插入时,能够用NULL或者适当的默认值来填充所有其他不属于该视图的属性。比如,SELECT语句中不允许包括被定义为非空或者没有默认值的属性。</li>
</ol>
<p>当然以上规则可不用严格遵守,但是还是建议少用视图来更新,比如有时候在视图上的更新应用到了真实表,但是由于插入的数据不满足视图的查询规则,所以你在视图上的更新操作并不会显示在视图中。</p>
<p><strong>注意</strong> 当然,人类还是聪明的,我们可以在视图中加入触发器,以此进行数据更新操作的修正或者限制,以此达到我们的目的。</p>
<h3 id="索引">索引</h3>
<p> 关系中属性A上的索引(index) 是一种数据结构,它能提高在属性A上查找具有某个特定值的元组的效率。大型关系的索引的实现技术是DBMS实现中最重要的核心问题。典型的DBMS中用到的最重要的数据结构是“B-树”, 它是一种广义上的平衡二叉树。当讨论DBMS的实现时必然涉及B-树细节,但是目前,只需把索引想象成二叉查找树就足够了。</p>
<h4 id="sql中的索引">SQL中的索引</h4>
<ul>
<li>
<p>创建索引:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">YearIndex</span> <span class="k">ON</span> <span class="n">Movies</span><span class="p">(</span><span class="nb">year</span><span class="p">);</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">KeyIndex</span> <span class="k">ON</span> <span class="n">Movies</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="nb">year</span><span class="p">);</span>
</code></pre></div> </div>
</li>
<li>
<p>删除索引:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">INDEX</span> <span class="n">YearIndex</span><span class="p">;</span>
</code></pre></div> </div>
</li>
</ul>
<h4 id="索引选择">索引选择</h4>
<p> 选择创建哪个索引要求数据库设计者做一个开销上的分析。实际上,索引的选择是衡量
数据库设计成败的一个重要因素。设计索引时要考虑以下两个重要因素:</p>
<ul>
<li>如果属性上存在索引,则为该属性指定一个值或者取值范围能极大地提高查询的执行效
率。同样,如果查询涉及该属性上的连接操作,也会带来性能上的改善。</li>
<li>另一方面,为关系上的某个属性或者某个属性集建立的索引会使得对关系的插人、删除
和修改变得更复杂和更费时。</li>
</ul>
<p><strong>注意</strong>:一般来讲,我们在键上进行索引就足够了,还有就是查询频繁的字段也加上索引,这里面涉及到很多数据库调优的知识了,一般也是结合项目来进行所以不作过多分析,有兴趣自己看书吧。</p>
<h3 id="物化视图">物化视图</h3>
<p> 视图描述了如何通过在基表上执行查询来构造一个新的关系。 到目前为止,仅仅认为视图是关系的-一个逻辑上的描述,然而,如果一个视图被经常使用,则可能会想到将它物化(materialize),即在任何时间都保存它的值。因为当基本表发生变化时,每次必须重新计算部分物化视图,所以就像维护索引一样,维护物化视图也要一定的代价。</p>
<p>例如:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="n">MATERIALIZED</span> <span class="k">VIEW</span> <span class="n">MovieProd</span> <span class="k">AS</span>
<span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span> <span class="nb">year</span> <span class="p">,</span> <span class="n">name</span>
<span class="k">FROM</span> <span class="n">Movies</span><span class="p">,</span> <span class="n">MovieExec</span>
<span class="k">WHERE</span> <span class="n">producerC</span> <span class="o">=</span> <span class="n">cert</span><span class="p">;</span>
</code></pre></div></div>
<p><strong>注意</strong> 物化视图好像是固化视图,就是相当于创建一张新表,所以对于物化视图的维护都是要额外进行SQL语句执行的,由于mysql中没有这个特性,网上大家都是新建表来进行的,所以我也只是猜测,没法验证,因此暂时这样理解吧。</p>
<p>附录1:</p>
<blockquote>
<p>符号读法(便于输入法快速打出)<br />
1 Α α alpha alf 阿尔法<br />
2 Β β beta bet 贝塔<br />
3 Γ γ gamma gam 伽马<br />
4 Δ δ delta delt 德尔塔<br />
5 Ε ε epsilon epsilon 伊普西龙 <br />
6 Ζ ζ zeta zat 截塔 <br />
7 Η η eta eit 艾塔 <br />
8 Θ θ thet θit 西塔<br />
9 Ι ι iot aiot 约塔<br />
10 Κ κ kappa kap 卡帕<br />
11 ∧ λ lambda lambd 兰布达<br />
12 Μ μ mu mju 缪<br />
13 Ν ν nu nju 纽<br />
14 Ξ ξ xi ksi 克西<br />
15 Ο ο omicron omikron 奥密克戎<br />
16 ∏ π pi pai 派<br />
17 Ρ ρ rho rou 肉<br />
18 ∑ σ sigma sigma 西格马<br />
19 Τ τ tau tau 套<br />
20 Υ υ upsilon jupsilon 宇普西龙<br />
21 Φ φ phi fai 佛爱<br />
22 Χ χ chi phai 西<br />
23 Ψ ψ psi psai 普西<br />
24 Ω ω omega omiga 欧米伽</p>
</blockquote>woodcoding摘要 大学里学的数据库课程也差不多忘了(好像根本没怎么听,就只记得个笛卡尔积和几大范式了哈哈),所以就学习一下。当然,如果对离散数学没有任何认识的话还是建议先看离散数学,因为数据库中很多术语都在离散数学中有定义。 更新记录: 2020-03-20: 开始记录 2020-03-26: 完成一、二、三章,把范式搞清楚就好办了 2020-03-27: 完成第四章,E/R图完成,第一部分理论就完成了,准备进入数据库程序设计部分。 2020-04-03: 完成第五章、第六章,基本上重点内容都已经记录到位,后面花个几天把尾巴补上。 2020-04-06: 完成第七章、第八章,对于数据库的基本操作差不多就结束了,后面两个章节都是些数据库的应用了,像我们这种CRUD程序员平时用到的情况也不多,所以可能就挑一些重点内容学习下。 待完成 第一章 数据库系统世界 第二章 关系数据模型 第三章 关系数据库设计理论(含范式判断) 第四章 高级数据库模型(含E/R图) 第五章 代数和逻辑查询语言 第六章 数据库语言SQL 第七章 约束与触发器 第八章 视图与索引最近在做的一些事和计划2020-03-03T21:30:00+00:002020-03-03T21:30:00+00:00http://blog.woodcoding.com/%E6%97%A5%E5%B8%B8/2020/03/03/something-recent-did-and-plan<h3 id="摘要">摘要</h3>
<p> 害,又是一周多没写博客了,失踪人口回归。主要是最近任(玩)务(的)繁(挺)忙(嗨),买了两块iot的板子,然后尝试在写一个能用的flask项目,所以沉浸在新事物的乐趣之中无法自拔。下面说说最近干的事和接下来的一些计划。</p>
<!-- more -->
<h3 id="一最近干的一些事">一、最近干的一些事</h3>
<h4 id="1玩转iot之esp物联网板子">1、玩转IOT之ESP物联网板子</h4>
<p> 没错,又菜又爱玩说的就是我了。前阵子发现家里废旧电子产品还挺多的,然后就改造了两个显示屏,一个给老妈装上电视盒子看电视了,一个自己用来当副屏用。于是突发奇想,能不能把家里的这些东西给废物利用一下,毕竟拿去换不锈钢脸盆还是挺心疼的。<br />
说干就干,掏出吃灰已久的树莓派,刷固件,开机,嗯,又放回去了。SD卡好像老化了,虽然是3b+,但是速度感人,过阵子买个新的SD卡来再搞吧。打开淘宝,搜索,聪明智慧又帅气的人适合用什么物联网开发板,然后显示:</p>
<p><img src="/resources/images/2020-03-03/20200303202529.png" alt="" /></p>
<p> 嗯,看这价格,真是让我等本不富裕的家庭雪上加霜,不过看在是为了聪明智慧又帅气的人推荐的,那我就勉为其难买下吧。顺便把一些周边都买了:
<img src="/resources/images/2020-03-03/20200303202957.png" alt="" /></p>
<p> 完了,吃土。。。但是不慌,我们来看成果:
当当当当, ip网关显示器:</p>
<video id="video" controls="" preload="none">
<source id="mp4" src="/resources/videos/2020-03-03/iot1.MP4" type="video/mp4" />
</video>
<p>当当当当, 蓝灯闪烁器:</p>
<video id="video" controls="" preload="none">
<source id="mp4" src="/resources/videos/2020-03-03/iot2.MP4" type="video/mp4" />
</video>
<p>然而并没有什么卵用,目前还没有想好要做啥,先留个坑,以后再填。</p>
<h4 id="2flask阅读与实战">2、Flask阅读与实战</h4>
<p> 最近粗略读完了一本Python web有名的“狼书”,就是李辉的《Flask Web开发实战:入门、进阶与原理解析》。虽然有些小瑕疵,但是不可否认的是,写的真的是挺好。有实战,有原理解析。相比于之前看过的一些捞钱书来说简直不要好太多。然后书上的一些模式设计,开发流程,还是需要实践一下,所以目前有在写一个小型的项目。这个项目的灵感来源于github上看到的一个用非python语言写的小工具,我用python+flask来copy一下,顺便把我的一些想法加进去,然后一边写一边体会狼书上关于flask开发的一些做法,权当实践了。</p>
<h3 id="二接下来的计划">二、接下来的计划</h3>
<ul class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />1.一些个人或小团队开发的实用工具介绍(git、ci、api等方面)</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />2.OAuth、LDAP等统一认证的一些知识点</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />3.物联网开发入坑系列</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />4.Flask的两个小项目</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />5.毕设重构(Django+Flask+Mongo)</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />6.一些框架原理的轮子实现(玩具版,主要是学原理)</li>
</ul>
<h4 id="1一些个人或小团队开发的实用工具介绍gitciapi等方面">1、一些个人或小团队开发的实用工具介绍(git、ci、api等方面)</h4>
<p> 今天阿里云客服给我打了电话,一个声音很甜的小姐姐,说我的账号是她在服务,如果我有什么需要可以直接找她。嘿嘿嘿。不过算起来,我从大一开始买云服务器玩,到现在已经4年了。加上去年蜗牛星际的矿难,买了个矿机用来当NAS(现在还被炒的涨价了,早知道当初多囤点货了)。杂七杂八也搭建了很多服务,什么gitea、drone、yapi等工具,都是些轻量型的小工具,虽然像gitlab这样的重量级选手一键搞定,但是社区版有限制,用破解的又不太舒服,毕竟用来存代码的玩意儿,不敢乱用,所以对于个人或者小团队开发的话,一些开源的轻量级工具组合还是蛮好用的。过阵子不忙了写篇文章分享一下这些工具的使用心得。</p>
<h4 id="2oauthldap等统一认证的一些知识点">2、OAuth、LDAP等统一认证的一些知识点</h4>
<p> 这几天在开发项目的时候也在思考一个问题,每个系统都要一套用户体系,那认证怎么办,单点登录听过很多次了,平时各大网站也在用,那么能不能自己实现一遍呢?所以这部分得学习一下。</p>
<h4 id="3物联网开发入坑">3、物联网开发入坑</h4>
<p> 物联网这个东西是真的好玩,虽然我菜,但是我就是要玩,哈哈。</p>
<h4 id="4flask的两个小项目">4、Flask的两个小项目</h4>
<p> 最近实践狼书大概有半个月了,每天早上爬起来吃完饭就是干,然后午休起来又是干,干到晚上睡觉,然而这个项目还是只出了部分原型,功能还有很多残缺,后台也是直接集成了Flask-Admin,前期将就用着,后期再重新设计了。然后这个项目的数据库表结构其实和博客的数据库表结构差不多,所以打算后期将部分代码抽离出来重新造个博客轮子,毕竟我觉得这个UI还是蛮好看的。先献丑了:
<img src="/resources/images/2020-03-03/20200303210908.png" alt="" /></p>
<h4 id="5毕设重构djangoflaskmongo">5、毕设重构(Django+Flask+Mongo)</h4>
<p> 然后就是毕业设计了,名称是《ACM队员竞赛能力分析系统的设计与实现》,虽然我打比赛很菜,但是好歹这毕业设计我也是拿了个为数不多的“优”啊,这项目必须得上线,好好提升一下学弟学妹的能力,可不能像我这么菜了。
<img src="/resources/images/2020-03-03/20190323233835.png" alt="" /></p>
<h4 id="6一些框架原理的轮子实现玩具版主要是学原理">6、一些框架原理的轮子实现(玩具版,主要是学原理)</h4>
<p> “程序员如果不重复造轮子,那将毫无乐趣!” 鲁迅如是说。</p>woodcoding摘要 害,又是一周多没写博客了,失踪人口回归。主要是最近任(玩)务(的)繁(挺)忙(嗨),买了两块iot的板子,然后尝试在写一个能用的flask项目,所以沉浸在新事物的乐趣之中无法自拔。下面说说最近干的事和接下来的一些计划。Flask锚点多表单实现与HTTP300重定向系列状态码2020-02-21T22:30:00+00:002020-02-21T22:30:00+00:00http://blog.woodcoding.com/flask/2020/02/21/http-300-code-with-flask-form-anchor<h3 id="摘要">摘要</h3>
<p> 今天遇到了一个Flask重定向的问题,在此分享一下。问题情景:在一个前后端不分离的页面下,用Tab标签做了多个页面切换,每个页面有自己的表单数据,页面是统一一个地方加载的,假设为Profile视图,但是处理表单数据分了多个视图,如change-password,change-email。那么问题来了,如何实现正确的加载页面与数据处理操作呢?虽然这个问题很傻逼,可能一时还理不清这其中有啥问题,并且在前后端分离的情况下这根本不是一个问题。但是今天本着“作死”到底的精神,我们可以来详细分析一下。</p>
<!-- more -->
<h3 id="问题分析">问题分析</h3>
<p>如果上面的文字分析还不能理解清楚,那我们先来上一张图看看。</p>
<p><img src="/resources/images/2020-02-19/profile.png" alt="" /></p>
<p>如上图所示,这是我们的一个多标签页的页面,这是用bootstrap生成的页面,多个标签之间的切换采用锚点来实现。我们在profile视图中渲染了这个页面,代码如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">user_bp</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/profile/'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">,</span> <span class="s">'POST'</span><span class="p">])</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">profile</span><span class="p">():</span>
<span class="n">context</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
<span class="p">...</span>
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">'user/profile.html'</span><span class="p">,</span> <span class="o">**</span><span class="n">context</span><span class="p">)</span>
</code></pre></div></div>
<p>用户可以在这个页面自由切换“个人信息”、“修改密码”等不同标签页。但是现在的问题是,我们不可能把这个标签页中所有的表单处理逻辑全放入profile函数之中,所以我们要抽离不同页面的表单处理逻辑,代码如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">user_bp</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/profile/change-password/'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">,</span> <span class="s">'POST'</span><span class="p">])</span>
<span class="o">@</span><span class="n">fresh_login_required</span>
<span class="k">def</span> <span class="nf">change_password</span><span class="p">():</span>
<span class="k">pass</span>
<span class="o">@</span><span class="n">user_bp</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/profile/change-info/'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">,</span> <span class="s">'POST'</span><span class="p">])</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">change_password</span><span class="p">():</span>
<span class="k">pass</span>
</code></pre></div></div>
<p>这里处理之后的逻辑就是,用户在profile视图加载所有的操作页面,对应不同的表单处理可以通过前端action传递到不同的视图,如change-password,然后由change-password处理完成之后再返回操作前的标签页面,如果表单的值验证不符则显示对应的提示。</p>
<p>所以这里就有两个问题需要处理:</p>
<ul>
<li>1、如何在处理完成后跳转到对应的标签视图?</li>
<li>2、重定向之后如何显示对应的标签页表单验证的错误提示?</li>
</ul>
<h3 id="问题解决">问题解决</h3>
<p>问题找到了,如何解决?</p>
<ul>
<li>问题1:如何在处理完成后跳转到对应的标签视图?</li>
</ul>
<p>首先,我们处理完成之后肯定是要进行跳转的,所以代码可以这样写:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">redirect</span><span class="p">(</span><span class="n">url_for</span><span class="p">(</span><span class="s">'.profile'</span><span class="p">))</span>
</code></pre></div></div>
<p>直接跳转到profile视图,这里会存在一个问题,如果直接跳转,只会显示第一个标签页,想要显示对应的标签页还需要加上一个参数,如下,加入锚点值(锚点值其实应该是前端应该关注的内容,是前端用来定位元素的):</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">redirect</span><span class="p">(</span><span class="n">url_for</span><span class="p">(</span><span class="s">'.profile'</span><span class="p">,</span> <span class="n">_anchor</span><span class="o">=</span><span class="s">'change-password'</span><span class="p">))</span>
</code></pre></div></div>
<ul>
<li>问题2:重定向之后如何显示对应的标签页表单验证的错误提示?</li>
</ul>
<p>问题一很好解决,查文档之后就知道添加anchor参数,但是随即问题又来了。你会发现,如果表单验证不通过,他就是直接跳转到页面,不知道的还以为自己只是刷新了一下页面,用户体验极差。于是就debug了一下,发现重定向的时候数据完全没带过去,所以profile视图的表单也验证不了数据。经过一番面向搜索引擎编程之后,发现原来flask的redirect默认的http状态码是302,这里我们也可以从阅读flask源码中了解,在flask依赖的扩展werkzeug的utils模块中,定义了redirect函数如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">redirect</span><span class="p">(</span><span class="n">location</span><span class="p">,</span> <span class="n">code</span><span class="o">=</span><span class="mi">302</span><span class="p">,</span> <span class="n">Response</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="s">"""Returns a response object (a WSGI application) that, if called,
redirects the client to the target location. Supported codes are
301, 302, 303, 305, 307, and 308. 300 is not supported because
it's not a real redirect and 304 because it's the answer for...
</span></code></pre></div></div>
<p>可以看到code确实默认是302的。不过问题又来了,重定向和这个302之间有啥关系呢?这里就牵涉到了一些HTTP协议相关的知识,有兴趣的可以去读一读图灵系列图书《图解HTTP》,对web有点了解的画个几天的零碎功夫就看完了。我们知道HTTP状态码有很多种,常见的有成功响应的200系列,重定向的300系列,请求出错的400系列,服务器内部错误的500系列,在浏览器的开发者控制台也能看到请求一个网页的状态码:</p>
<p><img src="/resources/images/2020-02-19/chrome_status_code.png" alt="" /></p>
<p>我们返回的重定向函数并不是在服务器端的视图直接重定向,而是下发数据到浏览器,再由浏览器通过状态码来选择进行何种重定向操作。下面是我面向搜索引擎编程搞来的一些常见的重定向的解释:</p>
<blockquote>
<p>300:Multiple Choice(多种选择)
该请求有多种可能的响应,用户代理或者用户必须选择它们其中的一个。服务器没有任何标准可以遵循去代替用户来进行选择。HTTP/1.0 以及以前的版本。</p>
</blockquote>
<blockquote>
<p>301:Moved Permanently(永久移动)
该状态码表示所请求的URI资源路径已经改变,新的URL会在响应的Location:头字段里找到。 HTTP/0.9 可用。</p>
</blockquote>
<blockquote>
<p>302:Found(临时移动)
该状态码表示所请求的URI资源路径临时改变,并且还可能继续改变。因此客户端在以后访问时还得继续使用该URI.新的URL会在响应的Location:头字段里找到。HTTP/0.9 可用。</p>
</blockquote>
<blockquote>
<p>303:See Other(查看其他位置)
服务器发送该响应用来引导客户端使用GET方法访问另外一个URL。 HTTP/0.9 和 1.1可用。</p>
</blockquote>
<blockquote>
<p>304:Not Modified(未修改)
告诉客户端,所请求的内容距离上次访问并没有变化。 客户端可以直接从浏览器缓存里获取该资源。HTTP/0.9 可用。</p>
</blockquote>
<blockquote>
<p>305:Use Proxy(使用代理)
所请求的资源必须统过代理才能访问到。由于安全原因,该状态码并未受到广泛支持。HTTP/1.1 可用。</p>
</blockquote>
<blockquote>
<p>306:unused(未使用)
这个状态码已经不再被使用,当初它被用在HTTP 1.1规范的旧版本中。HTTP/1.1 可用。</p>
</blockquote>
<blockquote>
<p>307:Temporary Redirect(临时重定向)
服务器发送该响应,用来引导客户端使用相同的方法访问另外一个URI来获取想要获取的资源。新的URL会在响应的Location:头字段里找到。与302状态码有相同的语义,且前后两次访问必须使用相同的方法(GET POST)。HTTP/1.1 可用。</p>
</blockquote>
<blockquote>
<p>308:Permanent Redirect(永久重定向)
所请求的资源将永久的位于另外一个URI上。新的URL会在响应的Location:头字段里找到。与301状态码有相同的语义,且前后两次访问必须使用相同的方法(GET POST)。HTTPbis(试验草案)</p>
</blockquote>
<p>而在大部分浏览器用的302会将POST转为GET再重定向,这里我们使用307状态码就可以将post的数据直接传给重定向之后的视图了。至于你说为什么307能做到这样?大家规定就个状态码就是这样的操作,我也没办法。</p>woodcoding摘要 今天遇到了一个Flask重定向的问题,在此分享一下。问题情景:在一个前后端不分离的页面下,用Tab标签做了多个页面切换,每个页面有自己的表单数据,页面是统一一个地方加载的,假设为Profile视图,但是处理表单数据分了多个视图,如change-password,change-email。那么问题来了,如何实现正确的加载页面与数据处理操作呢?虽然这个问题很傻逼,可能一时还理不清这其中有啥问题,并且在前后端分离的情况下这根本不是一个问题。但是今天本着“作死”到底的精神,我们可以来详细分析一下。获取百度搜索结果时解决人机滑动验证码的问题2020-02-12T23:19:00+00:002020-02-12T23:19:00+00:00http://blog.woodcoding.com/%E7%88%AC%E8%99%AB/2020/02/12/baidu-search-verify-code<h3 id="摘要">摘要</h3>
<p> 前阵子把毕设重构了一遍,爬虫部分涉及到了一个需求:获取一个oj题目在csdn或者博客园的题解链接。由于博客园和csdn都没有找到好的api可以调用,百度api也是收费的,所以就想能不能利用爬虫直接爬取百度搜索结果的前列,然后做百度url转真实解析。具体的一些设计思路后面再说,这里我们先来讨论爬虫中的一个问题,就是解决验证码的问题。首先由于requests尝试好几次都不行,所以换了selenium,虽然慢一点,但是至少有个解决方案了。但是百度的技术也不差,就检测到了我的爬虫行为(之后加了各种办法来避免这个问题,到写这篇文章的时候,本来想把验证码图给大家截一下,但是我不管怎么暴力爬百度还是不给验证码页面,这个以后遇到再补吧),就出现了滑动验证的人机检测。不过由于这个滑动验证码实在是太鸡肋了,就是直接滑倒最右边,所以相比于其他一些图形验证码来说还是比较简单的,废话说了一大堆,下面就来说说解决办法。</p>
<!-- more -->
<h3 id="前言">前言</h3>
<p> 首先我们来看一下这个滑动验证码长什么样子(百度的因为日志图太多被我删了,网上随便找了个差不多的,将就看一下吧)
<img src="/resources/images/2020-02-12/verify_code.png" alt="" /></p>
<p> 解决思路很简单,就是模拟人把这个按钮移到最右边。但是要考虑人移动滑块是有加速和减速的,不然还是会被检测为人机,所以我们待会用到初中物理的知识去解决他。</p>
<h3 id="step1获取到验证码界面的信息">Step1:获取到验证码界面的信息</h3>
<ul>
<li>获取滑动按钮的对象:</li>
</ul>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">btn_id</span> <span class="o">=</span> <span class="n">driver</span><span class="p">.</span><span class="n">find_element_by_class_name</span><span class="p">(</span><span class="s">'vcode-slide-button'</span><span class="p">).</span><span class="n">get_attribute</span><span class="p">(</span><span class="s">'id'</span><span class="p">)</span>
<span class="n">btn</span> <span class="o">=</span> <span class="n">driver</span><span class="p">.</span><span class="n">find_element_by_id</span><span class="p">(</span><span class="n">btn_id</span><span class="p">)</span>
</code></pre></div></div>
<ul>
<li>获取滑动条的长度:</li>
</ul>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">distance</span> <span class="o">=</span> <span class="n">driver</span><span class="p">.</span><span class="n">find_element_by_class_name</span><span class="p">(</span><span class="s">'vcode-slide-bottom'</span><span class="p">).</span><span class="n">size</span><span class="p">[</span><span class="s">'width'</span><span class="p">]</span> <span class="o">-</span> <span class="n">btn</span><span class="p">.</span><span class="n">size</span><span class="p">[</span><span class="s">'width'</span><span class="p">]</span> <span class="o">+</span> <span class="n">random</span><span class="p">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
</code></pre></div></div>
<p> 这里我获取了滑动条长度,然后减去了按钮的宽度加了个随机长度。减去按钮宽度肯定是要的,然后这个随机长度是因为我发现直接滑这么长还是会被检测到,或者是出现有时候滑不到的情况,毕竟人哪有这么精准。</p>
<h3 id="step2-设计滑动路径">Step2: 设计滑动路径</h3>
<p>初中物理的路程、速度与加速度公式大家应该都熟记于心了。</p>
<p>$ V = V_0 + at $
$ S = V_0t +\frac{1}{2}at^2 $</p>
<p>然后我们先来看代码:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">get_track</span><span class="p">(</span><span class="n">distance</span><span class="p">):</span>
<span class="s">"""
利用加速度公式构建滑动路径
:param distance: 滑动距离
:return:
"""</span>
<span class="n">tracks</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">current</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">mid</span> <span class="o">=</span> <span class="n">distance</span> <span class="o">*</span> <span class="mi">3</span> <span class="o">/</span> <span class="mi">4</span>
<span class="n">t</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span> <span class="o">*</span> <span class="mf">0.01</span>
<span class="n">v</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">current</span> <span class="o"><</span> <span class="n">distance</span><span class="p">:</span>
<span class="n">a</span> <span class="o">=</span> <span class="mi">10</span> <span class="k">if</span> <span class="n">current</span> <span class="o"><</span> <span class="n">mid</span> <span class="k">else</span> <span class="o">-</span><span class="mi">20</span>
<span class="n">v0</span> <span class="o">=</span> <span class="n">v</span>
<span class="n">v</span> <span class="o">=</span> <span class="n">v0</span> <span class="o">+</span> <span class="n">a</span> <span class="o">*</span> <span class="n">t</span>
<span class="n">move</span> <span class="o">=</span> <span class="n">v0</span> <span class="o">*</span> <span class="n">t</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">a</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">t</span>
<span class="n">current</span> <span class="o">+=</span> <span class="n">move</span>
<span class="n">tracks</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">round</span><span class="p">(</span><span class="n">move</span><span class="p">))</span>
<span class="k">return</span> <span class="n">tracks</span>
</code></pre></div></div>
<p> 首先我们传入了一个distance作为滑动距离,然后获得一个加速和减速之间的一个过渡点,这里取$\frac{3}{4}$长度作为过度点。然后随机一个时间作为我们滑动所需要的时间,这里取了0.2s到0.4s之间一个随机值。然后我们通过基本公式计算出每次移动到的位置并加入到tracks列表里面,之后把tracks,即移动路径返回。这里修改不同的变量都可能导致移动轨迹的大幅变化,随自己需要调整即可。</p>
<h3 id="step3selenium模拟滑动">Step3:Selenium模拟滑动</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">move_to_right</span><span class="p">(</span><span class="n">button</span><span class="p">,</span> <span class="n">driver</span><span class="p">,</span> <span class="n">distance</span><span class="p">):</span>
<span class="s">"""
移动滑块到最右
:param button: 滑动按钮
:param driver: selenium驱动
:param distance: 滑动距离
:return:
"""</span>
<span class="n">tracks</span> <span class="o">=</span> <span class="n">get_track</span><span class="p">(</span><span class="n">distance</span><span class="p">)</span>
<span class="n">log</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"验证码移动轨迹:{msg}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">msg</span><span class="o">=</span><span class="n">tracks</span><span class="p">.</span><span class="n">__repr__</span><span class="p">()</span> <span class="o">+</span> <span class="s">'moving...'</span><span class="p">))</span>
<span class="n">action</span> <span class="o">=</span> <span class="n">ActionChains</span><span class="p">(</span><span class="n">driver</span><span class="p">)</span>
<span class="n">action</span><span class="p">.</span><span class="n">click_and_hold</span><span class="p">(</span><span class="n">button</span><span class="p">).</span><span class="n">perform</span><span class="p">()</span> <span class="c1"># 模拟按下
</span> <span class="n">begin</span> <span class="o">=</span> <span class="n">button</span><span class="p">.</span><span class="n">location</span><span class="p">[</span><span class="s">'x'</span><span class="p">]</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">tracks</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">action</span><span class="p">.</span><span class="n">move_by_offset</span><span class="p">(</span><span class="n">xoffset</span><span class="o">=</span><span class="n">x</span><span class="p">,</span> <span class="n">yoffset</span><span class="o">=</span><span class="mi">0</span><span class="p">).</span><span class="n">perform</span><span class="p">()</span> <span class="c1"># 模拟移动
</span> <span class="n">now</span> <span class="o">=</span> <span class="n">button</span><span class="p">.</span><span class="n">location</span><span class="p">[</span><span class="s">'x'</span><span class="p">]</span> <span class="o">-</span> <span class="n">begin</span>
<span class="n">process</span> <span class="o">=</span> <span class="nb">round</span><span class="p">((</span><span class="n">now</span> <span class="o">/</span> <span class="n">distance</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">log</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">'当前验证码进度:{process}%'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">process</span><span class="o">=</span><span class="n">process</span><span class="p">))</span>
<span class="k">except</span> <span class="n">selenium_error</span><span class="p">.</span><span class="n">StaleElementReferenceException</span><span class="p">:</span>
<span class="k">break</span>
<span class="n">process</span> <span class="o">=</span> <span class="mf">100.00</span>
<span class="n">log</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">'当前验证码进度:{process}%'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">process</span><span class="o">=</span><span class="n">process</span><span class="p">))</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>
<p> 这里我们拿到滑动的按钮和selenium对象,以及上面第二部计算出的路径值,然后用selenium自带的ActionChains函数执行动作。先模拟按压,然后不断移动。中间可能出现验证码完成验证但是还会移动的情况(随机出现),所以当我们获取不到滑动元素的时候说明验证码已经完成验证了。然后就可以愉快的获取你想要的东西啦。</p>woodcoding摘要 前阵子把毕设重构了一遍,爬虫部分涉及到了一个需求:获取一个oj题目在csdn或者博客园的题解链接。由于博客园和csdn都没有找到好的api可以调用,百度api也是收费的,所以就想能不能利用爬虫直接爬取百度搜索结果的前列,然后做百度url转真实解析。具体的一些设计思路后面再说,这里我们先来讨论爬虫中的一个问题,就是解决验证码的问题。首先由于requests尝试好几次都不行,所以换了selenium,虽然慢一点,但是至少有个解决方案了。但是百度的技术也不差,就检测到了我的爬虫行为(之后加了各种办法来避免这个问题,到写这篇文章的时候,本来想把验证码图给大家截一下,但是我不管怎么暴力爬百度还是不给验证码页面,这个以后遇到再补吧),就出现了滑动验证的人机检测。不过由于这个滑动验证码实在是太鸡肋了,就是直接滑倒最右边,所以相比于其他一些图形验证码来说还是比较简单的,废话说了一大堆,下面就来说说解决办法。