PyQt5-Beginner-tutorial-part 2

原文链接

引言

这篇文章是前作的续篇(part 2), 为了达到最好的教学效果, 请务必先阅读前作.

在这篇续作当中, 我们依旧是通过实例代码来学习PyQt5, 我会涉及到下一篇教程(part 3)的一些内容.

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QGridLayout, QHBoxLayout, QLabel, QLineEdit,
QMessageBox, QPushButton, QTextEdit, QVBoxLayout, QWidget)

class SortedDict(dict):
class Iterator(object):
def __init__(self, sorted_dict):
self._dict = sorted_dict
self._keys = sorted(self._dict.keys())
self._nr_items = len(self._keys)
self._idx = 0

def __iter__(self):
return self

def next(self):
if self._idx >= self._nr_items:
raise StopIteration

key = self._keys[self._idx]
value = self._dict[key]
self._idx += 1

return key, value

__next__ = next

def __iter__(self):
return SortedDict.Iterator(self)

iterkeys = __iter__

class AddressBook(QWidget):
def __init__(self, parent=None):
super(AddressBook, self).__init__(parent)

self.contacts = SortedDict()
self.oldName = ''
self.oldAddress = ''

nameLabel = QLabel("Name:")
self.nameLine = QLineEdit()
self.nameLine.setReadOnly(True)

addressLabel = QLabel("Address:")
self.addressText = QTextEdit()
self.addressText.setReadOnly(True)

self.addButton = QPushButton("&Add")
self.addButton.show()
self.submitButton = QPushButton("&Submit")
self.submitButton.hide()
self.cancelButton = QPushButton("&Cancel")
self.cancelButton.hide()
self.nextButton = QPushButton("&Next")
self.nextButton.setEnabled(False)
self.previousButton = QPushButton("&Previous")
self.previousButton.setEnabled(False)

self.addButton.clicked.connect(self.addContact)
self.submitButton.clicked.connect(self.submitContact)
self.cancelButton.clicked.connect(self.cancel)
self.nextButton.clicked.connect(self.next)
self.previousButton.clicked.connect(self.previous)

buttonLayout1 = QVBoxLayout()
buttonLayout1.addWidget(self.addButton, Qt.AlignTop)
buttonLayout1.addWidget(self.submitButton)
buttonLayout1.addWidget(self.cancelButton)
buttonLayout1.addStretch()

buttonLayout2 = QHBoxLayout()
buttonLayout2.addWidget(self.previousButton)
buttonLayout2.addWidget(self.nextButton)

mainLayout = QGridLayout()
mainLayout.addWidget(nameLabel, 0, 0)
mainLayout.addWidget(self.nameLine, 0, 1)
mainLayout.addWidget(addressLabel, 1, 0, Qt.AlignTop)
mainLayout.addWidget(self.addressText, 1, 1)
mainLayout.addLayout(buttonLayout1, 1, 2)
mainLayout.addLayout(buttonLayout2, 3, 1)

self.setLayout(mainLayout)
self.setWindowTitle("Simple Address Book")

def addContact(self):
self.oldName = self.nameLine.text()
self.oldAddress = self.addressText.toPlainText()

self.nameLine.clear()
self.addressText.clear()

self.nameLine.setReadOnly(False)
self.nameLine.setFocus(Qt.OtherFocusReason)
self.addressText.setReadOnly(False)

self.addButton.setEnabled(False)
self.nextButton.setEnabled(False)
self.previousButton.setEnabled(False)
self.submitButton.show()
self.cancelButton.show()

def submitContact(self):
name = self.nameLine.text()
address = self.addressText.toPlainText()

if name == "" or address == "":
QMessageBox.information(self, "Empty Field",
"Please enter a name and address.")
return

if name not in self.contacts:
self.contacts[name] = address
QMessageBox.information(self, "Add Successful",
"\"%s\" has been added to your address book." % name)
else:
QMessageBox.information(self, "Add Unsuccessful",
"Sorry, \"%s\" is already in your address book." % name)
return

if not self.contacts:
self.nameLine.clear()
self.addressText.clear()

self.nameLine.setReadOnly(True)
self.addressText.setReadOnly(True)
self.addButton.setEnabled(True)

number = len(self.contacts)
self.nextButton.setEnabled(number > 1)
self.previousButton.setEnabled(number > 1)

self.submitButton.hide()
self.cancelButton.hide()

def cancel(self):
self.nameLine.setText(self.oldName)
self.addressText.setText(self.oldAddress)

if not self.contacts:
self.nameLine.clear()
self.addressText.clear()

self.nameLine.setReadOnly(True)
self.addressText.setReadOnly(True)
self.addButton.setEnabled(True)

number = len(self.contacts)
self.nextButton.setEnabled(number > 1)
self.previousButton.setEnabled(number > 1)

self.submitButton.hide()
self.cancelButton.hide()

def next(self):
name = self.nameLine.text()
it = iter(self.contacts)

try:
while True:
this_name, _ = it.next()

if this_name == name:
next_name, next_address = it.next()
break
except StopIteration:
next_name, next_address = iter(self.contacts).next()

self.nameLine.setText(next_name)
self.addressText.setText(next_address)

def previous(self):
name = self.nameLine.text()

prev_name = prev_address = None
for this_name, this_address in self.contacts:
if this_name == name:
break

prev_name = this_name
prev_address = this_address
else:
self.nameLine.clear()
self.addressText.clear()
return

if prev_name is None:
for prev_name, prev_address in self.contacts:
pass

self.nameLine.setText(prev_name)
self.addressText.setText(prev_address)

if __name__ == '__main__':
import sys

from PyQt5.QtWidgets import QApplication

app = QApplication(sys.argv)

addressBook = AddressBook()
addressBook.show()

sys.exit(app.exec_())

代码分析

我将会从第33行 Addressbook 这个类开始讲解, 因为大部分代码我在前作有讲解过, 我只会讲解前作没有涉及到的地方.

38-39行: 预先声明两个变量 oldNameoldAddress 以便后续代码调用.
42-43行: 我们声明一个文本编辑控件, 并且设置其只读属性为真, 当我们点击这个文本框的时候, 是无论如何都无法输入任何内容的.
49-58行: 我们为窗体添加一些按钮控件, 在第50行, show()方法会使 addButton 可见. 另外, submitButtoncancelButton 将会被我们隐式的创建. nextButtonpreviousButton 将会在窗体中显示出来, 但是他俩是灰色不可用的状态, 用户不能点击.
60-64行: 我们为控件添加点击触发的事件.
对于学习过前作的你来说, 看懂剩下的类简直是易如反掌了.
当我们第一次运行这个程序, 只有 Add 按钮是可以点击的, 这个按钮触发事件的代码在第87行.
88-89行: 还记得我们在38-39行定义的俩变量吗? 现在把 nameLineaddressText 的值赋给他俩.
91-96行: 我们把 nameLineaddressText 的文本框内容清空, 并且使用setReadOnly(false) 来让这俩变成可输入的状态, 最后把输入光标聚焦到nameLine 这里.
98-102行: 我们把 Add , PreviousNext 这三个按钮禁用掉, 然后吧 SubmitCancel 按钮设置为可见状态. 其中 cancel 按钮的出发事件代码从第137行开始.
138-139行: 还记得88-89行的那俩变量吗? 现在把 nameLineaddressText 的值赋回去.
141-143行: 但如果这些变量并没有存放任何数据, 我们便相应的设置文本框为空.
149-151行: self.contacts 是一个包含了我们的 address book 输入值的字典, 我们获取这个字典的大小并且赋给一个叫做 number 的变量, 如果输入的变量大于2个的话, 我们就把 nextButtonpreviousButton 设置为可用. 其中, Submit 按钮的触发事件代码在第104行开始.
105-106行: 把 nameaddress 这俩变量的值赋给文本框.
108-110行: 只要这些变量其中有为空值的, 我们就会报错.
113-120行: 这个字典的一个属性必须是唯一的键值对, 如果这个键值对不在我们的字典里面, 我们将会加到字典里并且显示一个 “success” 对话框. 同样的, 如果这个键值对存在于这个字典了, 我们就会显示错误. 其中, 字典的触发事件代码从第5行开始, 这个类会生成一个有序字典, 在代码的第36行被调用了.
7-11行: 初始化一些私有变量, _idx 是字典的索引, _nr_items 存放着字典的大小.
16-24行: 把列表进行排序, 当 _idx_nr_items 大的时候, 将会触发一个exception, 而正常情况我们会步进字典, 这个方法返回以index为索引, 自增长为1的键值对.
如果你想了解更多关于Python字典的内容, see this page
157-158行: 这个方法会在我们点击 next 按钮的时候触发, 输入框的值会赋给 name 变量, iter() 方法会给字典存在的变量赋值, 返回不在字典的键.
160-168行: 我们同样准备了 try-catch 代码, 在第162行的try语句里, 我们给键值对赋值.
170-171行: 我们把 next_name , next_address 的值赋给 nameLineaddressText. 其中 previous 按钮的触发事件代码从第173行开始.
第176行: 初始化 prev_name , prev_address 这两个变量, 并赋值为None.
177-182行: 我们迭代字典来储存 this_name , this_address 这两个变量的值, 如果 this_name 等于 nameLine 里面的值, 我们就退出迭代, 反之则把 prev_name , prev_address 赋给我们得到的变量.

189-190行: pass 这条语句本身毫无意义, 只是确保语法正确, 所以当 prev_name 为空时, 我们默认设置其值为空字符串.

总结

希望你能喜欢这篇文章 :-)

递归创建级联目录之Python_VS_PHP

引子

学习php的商城开发遇到递归创建级联目录问题,在学习了php下面的解决方法后,不禁想用Python来实现一下

代码

PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php 

// PHP5以后的版本内置mkdir函数支持创建级联目录!
echo mkdir($pathname='a/s/d/f/g/h/j/k/l', $mode=0777, $recursive=true);

// 递归创建级联目录两个版本
// 易理解, 语法复杂版本
function mk_dir1($dir) {
if (is_dir($dir)) {
return true;
}
if (is_dir(dirname($dir))) {
return mkdir($dir);
} else {
mk_dir1(dirname($dir));
return mkdir($dir);
}
}

// mk_dir1('a/s/d/f/g/h/j/');

// 难理解, 语法简洁版本
function mk_dir2($dir) {
if (is_dir($dir)) {
return true;
}
return (is_dir(dirname($dir))) || (mk_dir2(dirname($dir))) ? mkdir($dir) : false;
}

// mk_dir2('aa/ss/dd/ff/gg/hh/jj/');

?>

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os

# Python的os模块也是有带递归创建级联目录的方法 os.makedirs(name, mode=0o777, exist_ok=False)

def mk_dir(dirname):
if os.path.isdir(dirname):
return True
if os.path.isdir(os.path.dirname(dirname)) or os.path.dirname(dirname) == '':
return os.mkdir(dirname)
else:
mk_dir(os.path.dirname(dirname))
return os.mkdir(dirname)

mk_dir('1/2/3/5/6/9/8/7')

比较

相同点

二者实现思路一样,甚至使用函数都大同小异,另外二者皆有内置函数支持创建级联目录

不同点

从判断条件可以发现,php的dirname函数明显优于Python的os.path.dirname方法,后者在参数为单层目录的情况返回空字符串,而php返回表示当前目录的'.',这样导致Python需要多判断一种情况才能实现目标,希望Python后续版本可以借鉴一下php的这种方法~

当phantomJS遇上Requests

引子

前不久,学校弄了个SPOC网站,用JAVA写的,内容不多,但是网站的登录验证使用了少见的RSA算法对POST数据进行加密,不禁让我想到可否用Python来模拟用户登录,便有此文.

踩点

简单浏览网站后,得知加密算法是通过JavaScript实现,RSA键值对存放在登录页面html代码中的hidden属性的input标签里.

分析

获取RSA键值对对于Python是很容易的,因为是静态存在于html内,而对于动态的算法,Python则显得无力,这时候便祭出Python的拜把子兄弟PhantomJS,使用PhantomJS处理JavaScript部分的算法,返回加密后的数据给Python继续进行POST操作.

核心代码

JavaScript部分:

1
2
3
4
5
6
7
8
9
10
11
var system = require('system');
var modulus = system.args[1];
var exponent = system.args[2];
var tokenId = system.args[3];
var password = system.args[4]; // 密码
setMaxDigits(130);
key = new RSAKeyPair(exponent, "", modulus);
token = tokenId+"\n"+password;
strToken = encryptedString(key, token);
console.log(strToken);
phantom.exit();

Python部分:

1
2
3
4
cmd = 'phantomjs E:\\demo.js' \
+ ' ' + modulus + ' ' + exponent + ' ' + tokenId + ' ' + password
strToken = os.popen(cmd).read().strip("\n")
print(strToken.encode()) # check spare spaces

总结

登录搞定了基本上相当于搞定了全部,之后你可以发帖,回帖,等等……玩法很多.甚至玩刺激点的可以把脚本挂上vps,然后……

PyMySQL与Django的结合

引子

最近学习Django框架, 是基于Python3的, 配置MySQL的时候出了点岔子, 因为MySQLdb目前还不能完美兼容Python3, 而Django的MySQL驱动只能识别MySQLdb, 于是便有此文

解决方法

使用支持Python3的PyMySQL

而最关键的一点在于, 在站点目录下的init.py文件里面加上

1
2
3
import pymysql

pymysql.install_as_MySQLdb()

这样就能让Django识别出伪装成MySQLdb的PyMySQL了

附录

Django关于MySQL的配置代码

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django', # 你的数据库名称
'USER': 'root',
'PASSWORD': '',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}

PyMySQL安装方法

1
pip install pymysql

Python3切换华为hi link设备数据开关

OOP版本

2015/12/15更新
OOP版本


POP版本

代码

先贴上代码,注释都写的比较详细了

import urllib.request
import http.cookiejar
import re
import hashlib
import base64

re_csrf_token = re.compile(r'(?<="csrf_token" content=").+(?="/>)') # 预编译匹配csrf_token的正则表达式
re_switch_status = re.compile(r'(?<=<dataswitch>).+(?=</dataswitch>)') # 预编译匹配csrf_token的正则表达式
re_response = re.compile(r'(?<=<response>).+(?=</response>)') # 预编译匹配csrf_token的正则表达式


main_url = 'http://192.168.8.1/html/unicomhome.html' # 用于获取csrf_token的url
login_url = 'http://192.168.8.1/api/user/login' # 登录api
dataswitch_url = 'http://192.168.8.1/api/dialup/mobile-dataswitch' # 数据开关api

user = 'admin' # 用户名
psw = 'hldh214' # 密码

cookie = http.cookiejar.CookieJar() # 用cookiejar存储cookies
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))

csrf_token = opener.open(main_url).read().decode()
g_requestVerificationToken = re_csrf_token.findall(csrf_token)

# 开始编码密码
psw1 = hashlib.sha256(psw.encode()).hexdigest()
psw2 = base64.b64encode(psw1.encode()).decode()
psw3 = user + psw2 + g_requestVerificationToken[0]
psw4 = hashlib.sha256(psw3.encode()).hexdigest().encode()
psd = base64.b64encode(psw4).decode()
# 编码结束

login_data = '''<?xml version="1.0" encoding="UTF-8"?><request><Username>''' + user + '''</Username><Password>''' + psd + '''</Password><password_type>4</password_type></request>''' # 构造post数据

opener.addheaders = [('__RequestVerificationToken', g_requestVerificationToken[0])] # 伪造csrf_token头

login = opener.open(login_url, login_data.encode()).read().decode() # 登录

switch_on_data = '''<?xml version="1.0" encoding="UTF-8"?><request><dataswitch>1</dataswitch></request>'''
switch_off_data = '''<?xml version="1.0" encoding="UTF-8"?><request><dataswitch>0</dataswitch></request>'''

switch_status = opener.open(dataswitch_url).read().decode()
switch_status = re_switch_status.findall(switch_status)[0]

if (not int(switch_status)):
    csrf_token = opener.open(main_url).read().decode()
    g_requestVerificationToken = re_csrf_token.findall(csrf_token)
    opener.addheaders = [('__RequestVerificationToken', g_requestVerificationToken[0])]
    data_switch_on = opener.open(dataswitch_url, switch_on_data.encode()).read().decode()
    response = re_response.findall(data_switch_on)[0]
    print(response)
else:
    csrf_token = opener.open(main_url).read().decode()
    g_requestVerificationToken = re_csrf_token.findall(csrf_token)
    opener.addheaders = [('__RequestVerificationToken', g_requestVerificationToken[0])]
    data_switch_off = opener.open(dataswitch_url, switch_off_data.encode()).read().decode()
    response = re_response.findall(data_switch_off)[0]
    print(response)

实现原理

分析网关网页的JavaScript代码,发现设备只验证动态生成的csrf_token,通过正则表达式获取之,添加至头信息,之后就是常规GET/POST操作了

使用方法

修改代码的第16-17行,输入自己设备的账号密码

  • 运行代码,输出OK即表示正常

PyQt5-Beginner-tutorial

原文链接

引言

这是一篇启蒙级的PyQt5教程,其目的是让你在很短的时间内入门PyQt.需要具备一些Python的基本知识.

PyQt是跨平台GUI工具包Qt的Python版本.是Python的GUI编程众多选择之一.其余的选择有PySide, PyGTK, wxPython, 和 Tkinter.

PyQt是开发非营利性(GPL协议)程序的利器.而如果你需要开发营利性程序,PySide很适合你,并且它是遵循LGPL协议的

安装 PyQt

你需要Python的最新版本(现在是 3.3.3 注1 ).确认安装完毕且添加了环境变量,添加环境变量在安装Python的时候可以选择添加.

以上完成以后,去Riverbank官网下载合适版本的可执行文件,安装的时候选择默认安装.

编写你的第一段代码

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class Form(QWidget):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)

        nameLabel = QLabel("Name:")
        self.nameLine = QLineEdit()
        self.submitButton = QPushButton("&amp;Submit")

        buttonLayout1 = QVBoxLayout()
        buttonLayout1.addWidget(nameLabel)
        buttonLayout1.addWidget(self.nameLine)
        buttonLayout1.addWidget(self.submitButton)

        self.submitButton.clicked.connect(self.submitContact)

        mainLayout = QGridLayout()
        # mainLayout.addWidget(nameLabel, 0, 0)
        mainLayout.addLayout(buttonLayout1, 0, 1)

        self.setLayout(mainLayout)
        self.setWindowTitle("Hello Qt")

    def submitContact(self):
        name = self.nameLine.text()

        if name == "":
            QMessageBox.information(self, "Empty Field",
                                    "Please enter a name and address.")
            return
        else:
            QMessageBox.information(self, "Success!",
                                    "Hello %s!" % name)

if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)

    screen = Form()
    screen.show()

    sys.exit(app.exec_())

代码分析

1-2行: import必要的模块

第4行: QWidget是PyQt5里面关于用户界面的基类,所以你通过继承QWidget这个基类来创建一个新Form类.

5-6行: QWidget的构造函数.构造函数无父类,这将被定义为一个窗口.

7-9行: 添加一个标签,一个文本编辑框和一个提交按钮.

12-15行: 添加一个QVBoxLayout.QVBoxLayout类可以使widgets竖直显示.

第17行: 为提交按钮添加一个事件,事件为函数submitContact().

19-21行: 添加一个QGridLayout.

23-24行: 在设置完窗口标题之后设置QGridLayout为主窗口默认布局.

第27行: 使用nameLine变量表示文本输入框内容.

29-35行: 当nameLine无内容时通过弹窗提示,当其有内容时则弹窗输出内容文本.

至于剩下的代码就容易理解了.我们实例化一个Form对象叫做screen.使用show()方法在屏幕上显示窗口.

然后我们开始程序主循环.这个循环会等待事件来处理,直到程序调用exit()方法或主窗口被销毁.sys.exit() 注2 方法可以完成一个完美的退出,释放内存资源.

执行这个脚本只需要输入

python

来完成:

总结

这是一篇入门级的教程.想要看看综合参考请点我


注解

注1. 原文发表于January 23, 2014

注2. 有关app.exec_()的下划线问题

URI和URL的区别

转自cnblogs:URI和URL的区别

这两天在写代码的时候,由于涉及到资源的位置,因此,需要在Java Bean中定义一些字段,用来表示资源的位置,比如:imgUrl,logoUri等等。但是,每次定义的时候,心里都很纠结,是该用imgUrl还是imgUri呢?

同样的,另外一个问题:String HttpServletRequest.getRequestURI();和StringBuffer HttpServletRequest.getRequestURL();返回的内容有何不同?为什么会如此?

带着这些问题到网上去搜了下,没发现让自己看了明白的解释,于是,想到了Java类库里有两个对应的类java.net.URI和java.net.URL,终于,在这两个类里的javadoc里找到了答案。

URIs, URLs, and URNs

首先,URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。而URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。而URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com。也就是说,URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。

在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的,schema必须被指定。

ok,现在回答文章开头提出的问题,到底是imgUrl好呢,还是imgUri好?显然,如果说imgUri是肯定没问题的,因为即使它实际上是url,那它也是uri的一种。那么用imgUrl有没有问题呢?此时则要看它的可能取值,如果是绝对路径,能够定位的,那么用imgUrl是没问题的,而如果是相对路径,那还是不要用ImgUrl的好。总之,用imgUri是肯定没问题的,而用imgUrl则要视实际情况而定。

第二个,从HttpServletRequest的javadoc中可以看出,getRequestURI返回一个String,“the part of this request’s URL from the protocol name up to the query string in the first line of the HTTP request”,比如“POST /some/path.html?a=b HTTP/1.1”,则返回的值为”/some/path.html”。现在可以明白为什么是getRequestURI而不是getRequestURL了,因为此处返回的是相对的路径。而getRequestURL返回一个StringBuffer,“The returned URL contains a protocol, server name, port number, and server path, but it does not include query string parameters.”,完整的请求资源路径,不包括querystring。

总结一下:URL是一种具体的URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。URI是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而URL则必须提供足够的信息来定位,所以,是绝对的,而通常说的relative URL,则是针对另一个absolute URL,本质上还是绝对的。

注:这里的绝对(absolute)是指包含scheme,而相对(relative)则不包含scheme。

URI抽象结构 [scheme:]scheme-specific-part[#fragment]

[scheme:][//authority][path][?query][#fragment]

authority为[user-info@]host[:port]

参考资料:

http://docs.oracle.com/javase/1.5.0/docs/api/java/net/URI.html

http://en.wikipedia.org/wiki/Uniform_Resource_Identifier

http://docs.oracle.com/javaee/5/api/javax/servlet/http/HttpServletRequest.html

ps:

java.net.URL类不提供对标准RFC2396规定的特殊字符的转义,因此需要调用者自己对URL各组成部分进行encode。而java.net.URI则会提供转义功能。因此The recommended way to manage the encoding and decoding of URLs is to use java.net.URI. 可以使用URI.toURL()和URL.toURI()方法来对两个类型的对象互相转换。对于HTML FORM的url encode/decode可以使用java.net.URLEncoder和java.net.URLDecoder来完成,但是对URL对象不适用。