最近在做一些网络爬虫的时候,会经常用到正则表达式。为了写出正确的正则表达式,我经常在这个网站上进行测试:Regex Tester。这个页面上面一个输入框输入正则表达式,下面一个输入框输入测试数据,上面三个 checkBox 选择匹配模式,如果匹配正确,则将测试数据中匹配上的数据高亮。是一个很方便的工具网站。

我想,要是上不去网的时候想检测正则表达式的正确性该怎么办?不如自己写个小工具,无非就是一个界面,得到输入的正则表达式和测试数据,直接调用 Python 的 re 模块,匹配好后高亮一下就行。

最开始我准备用 wxPython 库来制作 GUI 界面,还去网上下了个《活学活用wxPython》,大致翻了下,发现用这个实现界面的话全部要自己写代码,感觉这样工作量一下就大了。某日我在使用 Python(x,y) 里面的 Spyder IDE 写脚本的时候,发现里面有个 Qt Designer,就点开看了下,这个居然能直接拖放控件来生成一个 GUI 界面,于是果断放弃使用 wxPython,转投 PyQt4。

首先,我照着前面提到的网页,大致画了个界面,包括三个 checkBox、两个 textEdit 和两个 label,分别放在三个 layout 里面大概就是下面这个界面:

保存后会得到一个扩展名为 .ui 的文件。比如我得到了一个 RegexTester.ui。

然后打开 cmd 命令行,切换到当前目录,输入以下命令: pyuic4 -o regexTesterUi.py RegexTester.ui,回车,就能根据画好的 ui 文件生成一个 py 文件。这时可以写一个测试脚本来运行一下这个界面。

 from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
import regexTesterUi class TestDialog(QDialog,regexTesterUi.Ui_Dialog):
def __init__(self,parent=None):
super(TestDialog,self).__init__(parent)
self.setupUi(self) if __name__ == '__main__':
app=QApplication(sys.argv)
dialog=TestDialog()
dialog.show()
app.exec_()

运行这个脚本,我们就可以得到刚才画的那个 GUI 界面,并且可以选中三个 checkBox ,在两个 textEdit 里面输入文本。只是除此之外没有任何功能。现在界面已经做好,我们需要做的就是实现高亮匹配数据的功能。

首先我们来完善一下这个类,我们需要的变量为输入的正则表达式、输入的测试数据、三个匹配模式(大小写敏感、多行匹配、点匹配所有)。

 class RegexTesterDialog(QtGui.QDialog, regexTesterUi.Ui_Dialog):

     def __init__(self, parent = None):
super(RegexTesterDialog, self).__init__(parent) self.CI = False # case insensitive (i)
self.MB = False # ^$ match at line breaks (m)
self.DM = False # dot matched all (s)
self.regex = ''
self.data = '' self.ui = regexTesterUi.Ui_Dialog()
self.ui.setupUi(self)

响应功能:

由于这个界面并没有按钮,我需要程序检测到任何一点变动就改变高亮的部分。这里就涉及到 Qt 的信号和槽机制。本文就不复述这些知识,具体可以参考 QT的信号与槽机制介绍。这里我们就要用到 QTextEdit 控件的 textChanged() 信号函数。具体的介绍可以查看 Qt 在线文档。这个信号函数在检测到 QTextEdit 控件中的文本发生了变化的时候会发射一个信号,与其关联的槽函数就会立即执行。而把这个信号函数和槽函数关联起来的方法就是 connect() 方法。这个网上也有很多介绍,这里我来介绍一个更 pythonic 的方法,使用 Python 的装饰器。PyQt中支持同名传递信号,就是说根据控件的名字来自动选择哪个槽。比如这里提到的 textChanged() 信号函数,如果要响应这个文本变化信号,可以这么做:

@QtCore.pyqtSlot()                            # 该装饰器标志此函数为接收信号的槽函数
def on_textEdit_Regex_textChanged(self): # 槽函数名标准格式 【on_控件名字_信号函数名字】
self.regex = self.ui.textEdit_Regex.toPlainText()
self.ui.textEdit_Data.setText(self.regex)

这里在槽函数上面加一个装饰器表示这个函数为接收信号的槽函数,然后根据控件名和信号函数名命名一个槽函数,这里我的接收正则表达式输入的 QTextEdit 控件名为 textEdit_Regex,因此这个槽函数名为 on_textEdit_Regex_textChanged。在这个槽函数里,我们通过 toPlainText() 方法得到文本框中的文本数据,然后将 textEdit_Data 中的数据设置为我们输入的值,这样就可以测试这个槽函数运行是否正确。当测试 textEdit_Data 控件的信号和槽函数时,也可以利用 textEdit_Regex 来输出结果。

除了 QTextEdit 控件,我们的界面还有 QCheckBox 控件。去查一下文档,可以找到这个控件的信号函数为 stateChanged(int)。我们发现这个函数带有一个参数,使用之前的方法会发现无法发射信号,这里我们需要在装饰器和槽函数中加入这个参数:

@QtCore.pyqtSlot(int)
def on_checkBox_CI_stateChanged(self, value):
if self.ui.checkBox_CI.isChecked():
self.CI = True
self.ui.textEdit_Data.setText(‘True’)
else:
self.CI = False
self.ui.textEdit_Data.setText(‘False')

虽然我们不知道这个参数是什么,但只要加进来,就可以正常使用。同理,碰到需要两个参数的信号函数时,只要再加一个参数就行。这里,当接收到 stateChanged(int) 信号时,我们使用 isChecked() 方法来检查控件是否被选中。如果选中了,则返回真,否则返回假。这里我们同样用到了 QTextEdit 控件来输出结果测试信号是否正确。其他两个 QCheckBox 控件同样设置。

匹配功能:

完成基本的响应函数之后,就要开始实现匹配功能。这里很简单,直接调用 re 模块,使用 findall() 方法。由于有三个 checkBox 提供三种匹配模式:

  • re.I (全拼:IGNORECASE): 忽略大小写(括号内是完整写法,下同)
  • re.M (全拼:MULTILINE): 多行模式,改变'^'和'$'的行为
  • re.S (全拼:DOTALL): 点任意匹配模式,改变'.'的行为

因此总共有 23 =8 种匹配模式:

 def matchData(self):
if (not self.CI) and (not self.MB) and (not self.DM):
pattern = re.compile(self.regex)
elif (not self.CI) and (not self.MB) and (self.DM):
pattern = re.compile(self.regex, re.S)
elif (not self.CI) and (self.MB) and (not self.DM):
pattern = re.compile(self.regex, re.M)
elif (not self.CI) and (self.MB) and (self.DM):
pattern = re.compile(self.regex, re.M|re.S)
elif (self.CI) and (not self.MB) and (not self.DM):
pattern = re.compile(self.regex, re.I)
elif (self.CI) and (not self.MB) and (self.DM):
pattern = re.compile(self.regex, re.I|re.S)
elif (self.CI) and (self.MB) and (not self.DM):
pattern = re.compile(self.regex, re.I|re.M)
elif (self.CI) and (self.MB) and (self.DM):
pattern = re.compile(self.regex, re.I|re.M|re.S) dataMatched = re.findall(pattern, self.data)

这里我们就可以得到匹配好的一个列表。刚开始实现这部分的时候,由于 Python 的 re 模块接收的参数类型是 Python string,而 PyQt 中控件得到的数据是 QString,一直报错,我一度准备使用 Qt 的 QRegExp 类来进行正则表达式匹配。但是查找文档查了好久,只找到一个改变大小写敏感的函数,找不到设置多行匹配和点匹配所有的方法,于是我去 stackoverflow 上问了个问题:Can QRegExp do MULTILINE and DOTALL match? 得到了一个详细的答案,一个解决问题的简单方法就是使用 unicode() 方法将 QString 转换成 python string,而一般不用将 python string 转换成 QString,因为接收 QString 类型参数的函数会自动将python string 转换成 QString。这样,我们直接在两个 QTextEdit 控件的槽函数中将得到的文本数据转换成 python string,就可以交给 re 模块处理了。

@QtCore.pyqtSlot()
def on_textEdit_Regex_textChanged(self):
self.regex = unicode(self.ui.textEdit_Regex.toPlainText())
self.matchData()

通过单步调试,我们可以测试上述 dataMatched 列表中的数据是否正确,如果正确,我们就可以继续进行下一步,实现高亮功能。

高亮功能:

去网上搜索 PyQt 高亮,可以搜到这样一篇博文:pyqt的语法高亮实现(译) 。这里使用到了 QSyntaxHighlighter 类。查文档可以看到,这个类可以与 QTextEdit 控件绑定,然后重写这个类的 highlightBlock() 函数,当 QTextEdit 控件中的文本发生变化时,会自动调用 highlightBlock() 函数,来高亮特定的文本。在上面的博文中,作者在自己定义的 MyHighlighter 类(QSyntaxHighlighter 类的子类)的初始化函数中,定义了一些规则,比如高亮一些关键字或数字,并将这些类型和高亮格式(颜色、加粗等)存到一个列表中,然后在重新实现的 highlightBlock() 函数遍历这个列表,并利用 setFormat() 方法来实现高亮。根据这个思想,将自己定义的 Highlighter 类与对话框类的 textEdit_Data 对象绑定,在初始化函数中定义高亮的格式,然后在 highlightBlock() 函数中对传进来的匹配结果进行高亮。由于 highlightBlock() 函数是对象在接收到文本变化信号后自动调用的方法,这个函数的 text 参数就是与之绑定的 textEdit_Data 对象得到的文本。这里我遍历传进来的匹配结果,然后在 text 中查找结果的开始位置,然后根据起始位置和匹配结果长度对匹配上的数据更改格式进行高亮。

 class MyHighlighter(QtGui.QSyntaxHighlighter):

     def __init__(self, parent)                                 # parent即绑定的QTextEdit对象
QtGui.QSyntaxHighlighter.__init__(self, parent)
self.parent = parent
self.highlight_data = [] # 存储匹配结果的列表 self.matched_format = QtGui.QTextCharFormat() # 定义高亮格式
brush = QtGui.QBrush(QtCore.Qt.yellow, QtCore.Qt.SolidPattern)
self.matched_format.setBackground(brush) def highlightBlock(self, text):
index = 0
length = 0
for item in self.highlight_data:
index = text.indexOf(item, index + length)
length = len(item)
self.setFormat(index, length, self.matched_format) def setHighlightData(self, highlight_data):
self.highlight_data = highlight_data

这里我加了个 setHighlightData(self, highlight_data),用来在得到匹配结果后将结果传递到这个高亮类。

这时我们需要对之前的代码进行一些更改。

在对话框类 RegexTesterDialog 的初始化函数中加上高亮对象及它和 textEdit_Data 的绑定:

self.highlighter = MyHighlighter(self.ui.textEdit_Data)

在 matchData() 方法中加上向高亮类传递匹配结果:

self.highlighter.setHighlightData(dataMatched)

然后,每当槽函数执行的时候,说明数据或匹配模式发生了变化,这时需要重新匹配一次,在5个槽函数中都要调用 matchData() 方法,比如:

@QtCore.pyqtSlot(int)
def on_checkBox_DM_stateChanged(self, value):
if self.ui.checkBox_DM.isChecked():
self.DM = True
else:
self.DM = False
self.matchData() @QtCore.pyqtSlot()
def on_textEdit_Regex_textChanged(self):
self.regex = unicode(self.ui.textEdit_Regex.toPlainText())
self.matchData()

这时候运行,发现了很多问题。当 textEdit_Data 中的数据更改时,并不能实时高亮,必须再输入一个字母才会高亮。比如正则表达式为 'ab',输入的数据为 'cab',当输入 'cab' 的时候,'ab' 并不会高亮,需要再输入一个其他的字母才能高亮。当 textEdit_Regex 中的数据更改或三个 checkBox 状态更改时,高亮结果并不会改变。想了很久,发现问题是因为当 textEdit_Data 发射 textChanged() 信号时,高亮类的 highlightBlock() 方法会自动调用,但他高亮的匹配结果是上一次匹配结果,所以会出现延时高亮。而其他几个槽函数执行时,高亮类并不能接收到他们的信号,所以其他的状态改变时并不会改变高亮结果。查找文档,发现高亮类还有个函数 rehighlightBlock(),这里的介绍是再次高亮整个文件。由于每个槽函数都调用了 matchData() 方法,我们就需要在方法结尾将匹配结果传递到高亮类后手动刷新一下界面,加上一个 rehighlightBlock() 方法。

    dataMatched = re.findall(pattern, self.data)
self.highlighter.setHighlightData(dataMatched)
self.highlighter.rehighlight()

再次执行,发现虽然能实时高亮,但是 IDE 一直在报错,每当我在 textEdit_Data 中输入一个字符时,都会报一个 runtime error,大概意思是递归深度出错。去网上查了下,是出现了无限递归,超过了 Python 默认的递归深度。

再次查了下文档,并设断点调试,发现了问题:highlightBlock() 方法会在接收到 textEdit_Data 的 textChanged() 信号时执行,同时槽函数 on_textEdit_Data_textChanged() 也会执行。而这个槽函数中又间接调用了 rehighlightBlock() 方法,当高亮匹配结果后, textEdit_Data 的文本格式发生了变化(部分数据被高亮了,虽然文本没有变化),这时又会发射 textChanged() 信号,于是程序会在 highlightBlock() 和 rehighlightBlock() 两个方法之间不停地递归,直到超过默认的递归深度。这里我想到的解决方法就是利用高亮后虽然格式变了但数据不变的特点,在槽函数 on_textEdit_Data_textChanged() 中加一个条件判断:

@QtCore.pyqtSlot()
def on_textEdit_Data_textChanged(self):
self.data = unicode(self.ui.textEdit_Data.toPlainText())
if self.data != self.previous_data:
self.previous_data = self.data
self.matchData()

给对话框类加上一个成员 previous_data,来存储 textEdit_Data 变化前的数据。初始化时都默认为空字符串。当该槽函数接收到文本变化信号时,首先判断这个数据与之前的数据是否相等,若相等,说明只是进行了高亮,无需重新匹配;如不等,则先将新数据赋给 previous_data,再重新匹配。这样就能解决无限递归问题。

这时再测试三个 checkBox 控件。前两个运行正确,运行最后一个,也就是 DOTALL 模式出了问题。

如果我不选中 DOT ALL 选项时,由于 '.' 不会匹配换行符,所以他能正确高亮,会高亮每一行中的符合条件的数据。但是如果我选中的话,'.' 会匹配换行符,这时候一旦正则表达式中包含 '.*' 之类的东西就会匹配不上。

比如我的测试数据是:

abcd

efgh

这里是一个两行数据。正则表达式是 'b.*',当没选中 DOT ALL 时,他能正确高亮 'bcd',一旦选中却不能高亮任何数据,而正确结果应该是高亮 ‘b’ 之后所有数据。

再次单步调试,测试到 matchData() 方法时发现匹配结果正确,结果中包含有换行符。而运行到 highlightBlock() 函数时,发现了问题。这个函数会被调用两次。每一次传进来的 text 参数只包含其中的一行数据。比如上面的例子,匹配结果是 'bcd\nefgh',而第一次调用 highlightBlock() 函数时,text 值为 'abcd',这时查找匹配结果为找不到,第二次调用时,text 值为 'efgh',同样找不到。当时就觉得这个设定很奇葩。为啥不只调用一次,text 直接传递文本框中的所有文本呢?

没办法,设计者这么设计肯定有他的考虑,我只有在这个基础上修改,达到能高亮结果的目的。既然他分行处理,我也可以分行高亮。先查看匹配结果中是否有换行符,如果有,使用 split('\n') 将结果分割成几个部分,这样这几个部分就分别在不同行了。需要注意的是,由于函数会被调用多次,因此找不到的时候一定要将 index 值重新置 0,因为换行时匹配结果可能是从行开头就开始高亮,若 index 不置 0,还是会出现找不到的现象。于是,高亮类的 highlightBlock() 方法变为:

def highlightBlock(self, text):
index = 0
length = 0
for item in self.highlight_data:
if item.count('\n') != 0:
itemList = item.split('\n')
for part in itemList:
index = text.indexOf(part, index + length)
if index == -1:
index = 0
else:
length = len(part)
self.setFormat(index, length, self.matched_format)
else:
index = text.indexOf(item, index + length)
length = len(item)
self.setFormat(index, length, self.matched_format)

这样修改后就能正确高亮匹配结果了。

效果如下:

整个文件的源代码为:

 import re

 from PyQt4 import QtGui, QtCore
import sys
import regexTesterUi class MyHighlighter(QtGui.QSyntaxHighlighter): def __init__(self, parent): # parent即绑定的QTextEdit对象
QtGui.QSyntaxHighlighter.__init__(self, parent)
self.parent = parent
self.highlight_data = [] # 存储匹配结果的列表 self.matched_format = QtGui.QTextCharFormat() # 定义高亮格式
brush = QtGui.QBrush(QtCore.Qt.yellow, QtCore.Qt.SolidPattern)
self.matched_format.setBackground(brush) def highlightBlock(self, text):
index = 0
length = 0
for item in self.highlight_data:
if item.count('\n') != 0:
itemList = item.split('\n')
for part in itemList:
index = text.indexOf(part, index + length)
if index == -1:
index = 0
else:
length = len(part)
self.setFormat(index, length, self.matched_format)
else:
index = text.indexOf(item, index + length)
length = len(item)
self.setFormat(index, length, self.matched_format) def setHighlightData(self, highlight_data):
self.highlight_data = highlight_data class RegexTesterDialog(QtGui.QDialog, regexTesterUi.Ui_Dialog): def __init__(self, parent = None):
super(RegexTesterDialog, self).__init__(parent)
self.CI = False # case insensitive (i)
self.MB = False # ^$ match at line breaks (m)
self.DM = False # dot matched all (s)
self.regex = ''
self.data = ''
self.previous_data = ''
self.ui = regexTesterUi.Ui_Dialog()
self.ui.setupUi(self)
self.highlighter = MyHighlighter(self.ui.textEdit_Data) @QtCore.pyqtSlot(int)
def on_checkBox_CI_stateChanged(self, value):
if self.ui.checkBox_CI.isChecked():
self.CI = True
else:
self.CI = False
self.matchData() @QtCore.pyqtSlot(int)
def on_checkBox_MB_stateChanged(self, value):
if self.ui.checkBox_MB.isChecked():
self.MB = True
else:
self.MB = False
self.matchData() @QtCore.pyqtSlot(int)
def on_checkBox_DM_stateChanged(self, value):
if self.ui.checkBox_DM.isChecked():
self.DM = True
else:
self.DM = False
self.matchData() @QtCore.pyqtSlot() # 该装饰器标志此函数为接收信号的槽函数
def on_textEdit_Regex_textChanged(self): # 槽函数名标准格式 【on_控件名字_信号函数名字】,表示这个函数接收该控件的信号
self.regex = unicode(self.ui.textEdit_Regex.toPlainText())
self.matchData() @QtCore.pyqtSlot()
def on_textEdit_Data_textChanged(self):
self.data = unicode(self.ui.textEdit_Data.toPlainText())
if self.data != self.previous_data:
self.previous_data = self.data
self.matchData() def matchData(self):
if (not self.CI) and (not self.MB) and (not self.DM):
pattern = re.compile(self.regex)
elif (not self.CI) and (not self.MB) and (self.DM):
pattern = re.compile(self.regex, re.S)
elif (not self.CI) and (self.MB) and (not self.DM):
pattern = re.compile(self.regex, re.M)
elif (not self.CI) and (self.MB) and (self.DM):
pattern = re.compile(self.regex, re.M|re.S)
elif (self.CI) and (not self.MB) and (not self.DM):
pattern = re.compile(self.regex, re.I)
elif (self.CI) and (not self.MB) and (self.DM):
pattern = re.compile(self.regex, re.I|re.S)
elif (self.CI) and (self.MB) and (not self.DM):
pattern = re.compile(self.regex, re.I|re.M)
elif (self.CI) and (self.MB) and (self.DM):
pattern = re.compile(self.regex, re.I|re.M|re.S) dataMatched = re.findall(pattern, self.data)
self.highlighter.setHighlightData(dataMatched)
self.highlighter.rehighlight() if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog = RegexTesterDialog()
dialog.show()
sys.exit(app.exec_())

大家可以点击 这里 从百度网盘下载这个项目,包括上面的源码,设计的 ui 文件以及通过 ui 文件生成的 Python 代码。

或者直接去 GitHub 上下载: PyRegexTester

使用PyQt4制作一个正则表达式测试小工具的更多相关文章

  1. 用 Python 制作一个艺术签名小工具,给自己设计一个优雅的签名

    生活中有很多场景都需要我们签字(签名),如果是一些不重要的场景,我们的签名好坏基本无所谓了,但如果是一些比较重要的场景,如果我们的签名比较差的话,就有可能给别人留下不太好的印象了,俗话说字如其人嘛,本 ...

  2. 利用 Python 写一个颜值测试小工具

    我们知道现在有一些利用照片来测试颜值的网站或软件,其实使用 Python 就可以实现这一功能,本文我们使用 Python 来写一个颜值测试小工具. 很多人学习python,不知道从何学起.很多人学习p ...

  3. Python学习之旅:用Python制作一个打字训练小工具

    一.写在前面 说道程序员,你会想到什么呢?有人认为程序员象征着高薪,有人认为程序员都是死肥宅,还有人想到的则是996和 ICU. 别人眼中的程序员:飞快的敲击键盘.酷炫的切换屏幕.各种看不懂的字符代码 ...

  4. 正则表达式测试分析工具Expresso

    正则表达式测试分析工具Expresso 一个正则表达式的小工具--myRegexHelper   把以前做的一个功能抽取出来做成一个小的正则表达式测试工具.没什么特色,有两点功能: 一.方便的测试正则 ...

  5. ios 一个正则表达式测试(只可输入中文、字母和数字)

    一个正则表达式测试(只可输入中文.字母和数字) 在项目中碰到了正则表达式的运用,正则还是非常强大的,不管什么编程语言,基本上都可以用到.之前在用java时特别是对用户名或密码使用正则非常爽,写 脚本上 ...

  6. 使用vba做一个正则表达式提取文本工具

    测试中经常会遇到对数据的处理,比如我要删除某些特定数据,数据源是从网页请求中抓取,这时候可能复制下来一大堆内容,其中我们只需要特定的某些部分,笔者通常做法是拷贝到notepad++中处理,结合RegT ...

  7. 撸一个JS正则小工具

    写完正则在浏览器上检测自己写得对不对实在是不方便,于是就撸了一个JS正则小demo出来. demo demo展示 项目地址 代码部分 首先把布局样式先写好. <!DOCTYPE html> ...

  8. 微信小程序-从零开始制作一个跑步微信小程序

    来源:伯乐在线 - 王小树 链接:http://ios.jobbole.com/90603/ 点击 → 申请加入伯乐在线专栏作者 一.准备工作 1.注册一个小程序账号,得用一个没注册过公众号的邮箱注册 ...

  9. Python制作有道翻译小工具

    该工具主要是利用了爬虫,爬取web有道翻译的内容. 然后利用简易GUI来可视化结果. 首先我们进入有道词典的首页,并点击翻译结果的审查元素 之后request响应网页,并分析网页,定位到翻译结果. 使 ...

随机推荐

  1. python PEP8代码规范及问题

    最近刚刚接触Python,为了养成好习惯,尽量保证自己写的代码符合PEP8代码规范,下面是过程中报出的警告及解决方法,英文有些翻译不太准确见谅,会不断更新: PEP 8: module level i ...

  2. [BZOJ1588]营业额统计(Splay)

    Description 题意:给定 n个数,每给定一个数,在之前的数里找一个与当前数相差最小的数,求相差之和(第一个数为它本身) 如:5 1 2 5 4 6 Ans=5+|1-5|+|2-1|+|5- ...

  3. Java基础知识:Collection接口

    *本文是最近学习到的知识的记录以及分享,算不上原创. *参考文献见文末. 这篇文章主要讲的是java的Collection接口派生的两个子接口List和Set. 目录 Collection框架 Lis ...

  4. iview框架 两侧弹框 出现第二层弹框 一闪而过的问题

    分析原因:寡人怀疑可能是,两层弹出框 采用的是一个开关值,发生了覆盖 解决方式 是在第二层弹框外套层计时器 源代码如下: 修改后为:

  5. 6 json和ajax传递api数据

    1 2 3 4 https://swapi.co/ <h1>Hello Reqwest!</h1> <script> var a = {} reqwest({ ur ...

  6. Windows网络编程笔记2

    这一次看看重定向器和如何使用Netbios函数获取本机mac地址 5.获取Mac地址 利用NCBASTAT命令实现,适配器状态命令会返回一个 ADAPTER_STATUS结构,紧接着是大量 NAME_ ...

  7. linux环境搭建系列之tomcat安装步骤

    前提: Linux centOS 64位 JDK 1.7 安装包从官网上下载 安装Tomcat之前要先安装JDK. 我的JDK是1.7版本的,所以Tomcat版本也选了7的 1.新建目录tomcat ...

  8. git放弃修改&放弃增加文件

    1. 本地修改了一堆文件(并没有使用git add到暂存区),想放弃修改. 单个文件/文件夹: git checkout -- filename 所有文件/文件夹: git checkout . 2. ...

  9. PAT——乙级1032

    这些题也确实简单,但是我还是想做做,多熟悉一下C++,毕竟实践是检验真理的唯一标准,有很多小知识点自己做了才知道. 这个题是 1032 挖掘机技术哪家强 (20 point(s)) 为了用事实说明挖掘 ...

  10. 实用拜占庭容错算法PBFT

    实用拜占庭容错算法PBFT 实用拜占庭容错算法PBFT 96 乔延宏 2017.06.19 22:58* 字数 1699 阅读 4972评论 0喜欢 11 分布式架构遭遇的问题 分布式架构会遭遇到以下 ...