2007年9月10日
@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
-
IDE Settings
IDE settings are stored in the dedicated directories under the product home directory, depending on the platform. The product home directory name is composed of the product name and version.
For IntelliJ IDEA Community edition the folder name is .IdeaICXX.
For example:
- Windows
-
- <User home>\.IntelliJIdeaXX\config that contains user-specific settings.
- <User home>\.IntelliJIdeaXX\system that stores IntelliJ IDEA data caches.
<User home> in WindowsXP is C:\Documents and Settings\<User name>\; in Windows Vista it is C:\Users\<User name>\
- Linux
-
- ~/.IntelliJIdeaXX/config that contains user-specific settings.
- ~/.IntelliJIdeaXX/system that stores IntelliJ IDEA data caches.
- Mac OS
-
- ~/Library/Application Support/IntelliJIdeaXX contains the catalog with plugins.
- ~/Library/Preferences/IntelliJIdeaXX contains the rest of the configuration settings.
- ~/Library/Caches/IntelliJIdeaXX contains data caches, logs, local history, etc. These files can be quite significant in size.
- 9.0+~/Library/Logs/IntelliJIdeaXX contains logs
The config directory has several subfolders that contain xml files with your personal settings. You can easily share your preferred keymaps, color schemes, etc. by copying these files into the corresponding folders on another IntelliJ IDEA installation. Prior to copying, make sure that IntelliJ IDEA is not running, because it can erase the newly transferred files before shutting down.
The following is the list of some of the subfolders under the config folder, and the settings contained therein.
Locations of the Config, System, and Plugins directories can be modified in IntelliJ IDEA_home\bin\idea.properties file.
You will need to adjust the following parameters:
- idea.config.path
- idea.system.path
- idea.plugins.path
Increasing productivity
To increase productivity of IntelliJ IDEA, you can change settings that reside in the following locations (depending on your operating system):
Example. increasing heap size
For example, to increase IntelliJ IDEA heap size, you should copy the original .vmoptions file from /Applications/IntelliJ IDEA.app/bin/idea.vmoptions to~/Library/Preferences/IntelliJIdeaXX/idea.vmoptions, then modify the -Xmx setting.
For the older versions, the settings are stored in:
/Applications/IntelliJ IDEA.app/Contents/Info.plist
Managing case of unicode literals
11.1+
IntelliJ IDEA allows defining whether non-ascii characters should use literals like '\u00AB' or '\00ab'.
This behavior is controlled by the system property idea.native2ascii.lowercase. By default, upper case characters are used.
If it is desirable to use lower case characters, do the following (depending on your platform)
- 12.0+On Windows and *NIX: add the line
idea.native2ascii.lowercase=true
to the bin/idea.properties file, located under the product installation.
- On Mac OS:
Copy the file /Applications/IntelliJ IDEA.app/bin/idea.properties to ~/Library/Preferences/IntelliJIdeaXX/, open it for editing, and add the line
idea.native2ascii.lowercase=true
It is essential to create a copy, since the settings are replaced rather than added.
For the older versions of IntelliJ IDEA, open for editing the file /Applications/IntelliJ IDEA.app/Contents/Info.plist, and add the following code:
<key>idea.native2ascii.lowercase</key>
<string>true</string>
to the section <key>Properties</key> <dict> ... <dict>
To change IDEA running JDK, set <JVMVersion> to 1.7* in /Applications/IntelliJ IDEA 13.app/Contents/Info.plist
VM arguments settings:
-Xms512m
-Xmx512m
-Xmn164m
-XX:MaxPermSize=250m
-XX:ReservedCodeCacheSize=64m
-Xverify:none
-Xnoclassgc
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=85
-ea
from: http://blog.braegger.pw/5-ways-to-burn-out-programming/
I've only recently come out of my burnout, despite it happening years ago. It sucks. It sucks bad. But looking back, I can see many of the causes crystal clearly, that weren't so apparent at the time. Here's a list: 1. Think about your project and only the project Let's face it. Business wants you to make the best product you can "for our customers". You put off fun features for the sake of missing a deadline. You plan and analyze and break a project into sets of deliverables that then must be coded by a monkey (you). You demo it, gather feedback, iterate. All without thinking anything for yourself. But newsflash: you started programming because you thought it was fun, why not keep programming because it's fun? Take that little extra time to put in a feature you want. Challenge yourself a little bit in doing something you didn't think you could. Show it to everyone you know, and don't just ask for feedback, but brag about what you've done. 2. Have a negative attitude toward everything. You know Docker? It sucks. Who would trust their production environment to a new, unstable, toy. Go? Do I look like I want to write every library myself? Everything I need is already in PyPI. This project I'm working on is so caught up in office politics, it's never going to work. Jenkins? 2008 wants their tech back. It's really easy to fall into the "being critical" trap. It's easy to tell other people what the "wrong" choice is. I imagine it's because as software engineers, our job is so find faults in our applications and fix them. And if we don't find them, someone else finds them for us. But I don't think we need to be negative about our job, decisions that are being made (even if it's not our decision) and what we're working on. Some of the best projects I've worked on worked out that way because we had a great, positive team. We enjoyed showing up every day to work, told each other when we did awesome things, held back heavy-handed criticism and phrased it in a productive manner. 3. Use the tools you know, because you're faster that way So you're an uber expert in Java + Spring + Hibernate. Nobody can touch your python skillz. Every personal project you do should be in these, because all that matters is the business side of things, right? Wrong. While it definitely makes good business sense, you should prototype, play around, and become an expert in new tech, even if it's unvetted. While this might seem like obvious advice (it's repeated alllll the time), it becomes a lot harder to do as you grow more experienced. 4. Switch jobs often Otherwise known as "chasing butterflies". Getting bored with what you're working on? Have an itch? Time to dust off that resume! This is bad, bad, bad. When you have several short employments, it can usually help boost your salary quite a bit, but you are robbing yourself of: - Growing in the company (developer -> manager -> director)
- Gaining an expertise in a specific area. Considering it takes 4-6 years for a PhD student to get their PhD, that's a lot of time you need for learnin.
- You are having to start from scratch often.
- If you are a good developer, you have to "prove" yourself (people listen to you) all over again.
So how do these contribute to burnout? Your career stagnates, you don't develop your skills as deeply (only breadth), people dont trust you'll stay employed for a while, and you're constantly having to prove yourself. 5. Work long hours, ignore your life "You don't have to work a lot of hours, but some people choose to." You want to impress your boss. Hell, you want to impress yourself. So you go die-hard to meet an impossible deadline. You delivered the project on time, with all the extra features you wanted. You are the hero. High fives all around. And if you're lucky, you'll get that bonus. That's great the first time. But how about the second. And the third. It's a bomb, and you dont know how short the fuse is. Summary In short, it's easy to burnout. Do these 5 things, and you can burnout too.
0. 安装一系列支持工具
网络工具wget、curl、axel等
代码:
sudo apt-get install wget curl axel
其他常见工具
代码:
sudo apt-get install subversion git sysstat linux-base linux-tools-generic build-essential vim emacs unrar p7zip synaptic
安装常用数据库和NoSQL
代码:
sudo apt-get install mysql-client mysql-server mysql-workbench memcached redis-server
1. 首先添加webupd8的java源:
代码:
sudo add-apt-repository ppa:webupd8team/java
2. 更新源中的软件数据:
代码:
3. 安装java6或者java7, 当然也可以两者都装
代码:
sudo apt-get install oracle-java8-installer
sudo apt-get install oracle-java9-installer
4. 安装maven和ant
代码:
sudo apt-get install maven ant
本方法在UBUNTU 13.04版本中默认安装的是maven 3.0.4版本和ant 1.8版本。如有特殊版本嗜好,请自行到apache.org下载安装。
5. 配置Maven
功夫网内用户可参照下面的链接进行配置:
http://maven.oschina.net/help.html
其他地区用户可自行到股沟上问百度。
6. 下载安装一种主流 JAVA IDE。
(1) 下载安装 IntelliJ IDEA
访问, 根据需要选择版本。推荐选择Free 30-day trial的Ultimate版本,至于激活码嘛,问度娘,你懂的~
axel -n 10 "http://download.jetbrains.com/idea/ideaIU-14.1.3.tar.gz"
tar xzvf ideaIU-14.1.3.tar.gz
cd idea-IU-141.1010.3/bin
sh idea.sh
最后要提醒一句。对于在校学生,拥有.edu邮箱的朋友,可以用你们的.edu邮箱获取一年的jetbrains旗下所有IDE产品的一年免费使用权。详见
https://www.jetbrains.com/student/
一年过后,只要你的邮箱还能用,可以再次申请。
(2) 下载安装eclipse IDE。(alternative step)
访问http://www.eclipse.org/downloads/,根据需要选择IDE版本。推荐选择Eclipse IDE for Java EE Developers版本。可以根据系统情况选择安装32位或者64位
这里以下载64位为例
代码:
axel -n 10 "http://ftp.daum.net/eclipse//technology/epp/downloads/release/mars/R/eclipse-jee-mars-R-linux-gtk-x86_64.tar.gz" tar -zxvf eclipse-jee-luna-SR2-linux-gtk-x86_64.tar.gz
然后就是自己创建快捷方式到桌面之类的。
(3) 下载netbeans IDE. (alternative step)
访问www.netbeans.org,然后找到下载地址。
这里以下载8.0.2版本为例:
代码:
axel -n 10 "http://dlc-cdn.sun.com/netbeans/8.0.2/final/bundles/netbeans-8.0.2-linux.sh"
sh netbeans-8.0.2-linux.sh
把netbeans配置为全屏反锯齿模式:
代码:
cp netbeans.conf netbeans.conf.bak; awk -F'=' '{if($1=="netbeans_default_options"){print index($0,"useSystemAAFontSettings")?$0:substr($0,0,length($0))" -J-Dawt.useSystemAAFontSettings=on\""}else{print $0}}' netbeans.conf.bak > netbeans.conf
7. 如果想做快速原型或者玩一玩的话,也可以安装一下nodejs。很有意思。
代码:
sudo curl --silent --location https://deb.nodesource.com/setup_0.12 | sudo bash - sudo apt-get install nodejs
8. GraphDB 最近很火。装个neo4j试试看。
代码:
wget -O - http://debian.neo4j.org/neotechnology.gpg.key | apt-key add -
sudo echo 'deb http://debian.neo4j.org/repo stable/' > /etc/apt/sources.list.d/neo4j.list
sudo apt-get update
sudo apt-get install neo4j
@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
eclipse官方是有标准的mac版下载的,随便copy到一个目录就可以直接启动了(当然必须安装java运行环境)。但是,假如我们想把应用放到mac的标准目录下(也就是finder里的应用程序目录下),就必须自己手动来做了,方法如下:
1.在finder下,copy eclipse目录下的Eclipse.app(在finder里是看不到app这个后缀名的,必须用Command+i,才能看到这个后缀名)文件到“应用程序”
2.然后,点击右键,选择“显示包内容”
3.修改eclipse.ini文件
修改文件的开头部分
原始内容为:
-startup ../../../plugins/org.eclipse.equinox.launcher_1.3.0.v20130327-1440.jar
--launcher.library ../../../plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.1.200.v20130521-0416
修改后的内容为:
-startup /Users/johnny/work/eclipse/plugins/org.eclipse.equinox.launcher_1.3.0.v20130327-1440.jar
--launcher.library /Users/johnny/work/eclipse/plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.1.200.v20130521-0416
我mac的用户名johnny,在我的用户名下有个work目录,我把eclipse的原始目录放在work下面。
这样就可以在“应用程序”下,启动eclipse了
@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
from: http://segmentfault.com/q/1010000000124379
如果你想从别的 Git 托管服务那里复制一份源代码到新的 Git 托管服务器上的话,可以通过以下步骤来操作。 1). 从原地址克隆一份裸版本库,比如原本托管于 GitHub。 git clone --bare git://github.com/username/project.git 2). 然后到新的 Git 服务器上创建一个新项目,比如 GitCafe。 3). 以镜像推送的方式上传代码到 GitCafe 服务器上。 cd project.git git push --mirror git@gitcafe.com/username/newproject.git 4). 删除本地代码 cd .. rm -rf project.git 5). 到新服务器 GitCafe 上找到 Clone 地址,直接 Clone 到本地就可以了。 git clone git@gitcafe.com/username/newproject.git 这种方式可以保留原版本库中的所有内容。
@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
转自:http://blog.csdn.net/fenglibing/article/details/7083071
一个服务(service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按spi标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名。如有一个服务接口com.test.Service,其服务实现类为com.test.ChildService,那此时需要在META-INF/services中放置文件com.test.Service,其中的内容就为该实现类的全限定名com.test.ChildService,有多个服务实现,每一行写一个服务实现,#后面的内容为注释,并且该文件只能够是以UTF-8编码。
这种实现方式,感觉和我们通常的开发方式差不多,都是定义一个接口,然后子类实现父类中定义的方法,为什么要搞这么一套标准以及单独搞一个配置文件?这种方式主要是针对不同的服务提供厂商,对不同场景的提供不同的解决方案制定的一套标准,举个简单的例子,如现在的JDK中有支持音乐播放,假设只支持mp3的播放,有些厂商想在这个基础之上支持mp4的播放,有的想支持mp5,而这些厂商都是第三方厂商,如果没有提供SPI这种实现标准,那就只有修改JAVA的源代码了,那这个弊端也是显而易见的,也就是不能够随着JDK的升级而升级现在的应用了,而有了SPI标准,SUN公司只需要提供一个播放接口,在实现播放的功能上通过ServiceLoad的方式加载服务,那么第三方只需要实现这个播放接口,再按SPI标准进行打包成jar,再放到classpath下面就OK了,没有一点代码的侵入性。
以下是找到的几篇文章:
1、http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html,这个是官方的文档,有对service的详细介绍,包括规范以及一个简单的示例,这个是学习SPI必须看的文档;
注:http://docs.oracle.com/javase/1.4.2/docs/guide/jar/jar.html#Service%20Provider,这个是1.4中对Service Provider的介绍,加载服务是通过sun.misc.Service进行加载的,这个也有相应的示例,照做就OK;
2、Java的SPI机制:http://www.2cto.com/kf/201012/79868.html,这个是国人写的一篇示例文章,也挺不错,里面也有一个简单的示例;
3、Developing a Service Provider using Java API(Service Provider Interface):http://blog.csdn.net/fenglibing/article/details/7083526,这篇文章是转的alexa发表在blogspot上面的,也是一个开发SPI的示例,有兴趣的也可以看看;
4、Add Mp3 capabilities to Java Sound with SPI:http://www.javaworld.com/javaworld/jw-11-2000/jw-1103-mp3.html,这是一个比较老的例子,基于jdk1.3的,因为在jdk1.3的时候还没有支持mp3格式,只支持AU, AIF, MIDI, and WAV等格式,也是一个值得参考的示例。
我这边也写了一个简单得不能够再简单的示例,源码可以这里下载:http://download.csdn.net/detail/fenglibing/3939882
最后很重要一点:
如果想要覆盖某个Provider,可以在对应的META-INF/services的配置文件中加上新service的一行,或者也可以写在另一个有依赖关系的jar包中,只要和原来的Provider命名不同即可。加载顺序上可以考虑使用@Priority注解来调整加载的优先级。
@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
转自: http://blog.jobbole.com/59790/
对于系统和网络管理员来说每天监控和调试Linux系统的性能问题是一项繁重的工作。在IT领域作为一名Linux系统的管理员工作5年后,我逐渐 认识到监控和保持系统启动并运行是多么的不容易。基于此原因,我们已编写了最常使用的18个命令行工具列表,这些工具将有助于每个Linux/Unix 系统管理员的工作。这些命令行工具可以在各种Linux系统下使用,可以用于监控和查找产生性能问题的原因。这个命令行工具列表提供了足够的工具,您可以 挑选适用于您的监控场景的工具。 1.Top-Linux进程监控 Linux下的Top命令是一个性能监控程序,许多系统管理员常常用它来监控Linux性能,在许多Linux或者类Unix操作系统里都有这个命令。Top命令用于按一定的顺序显示所有正在运行而且处于活动状态的实时进程,而且会定期更新显示结果。这条命令显示了CPU的使用率、内存使用率、交换内存使用大小、高速缓存使用大小、缓冲区使用大小,进程PID、所使用命令以及其他。它还可以显示正在运行进程的内存和CPU占用多的情况。对系统管理员来说,top命令式是一个非常有用的,它可用于监控系统并在需要的时候采取正确的处理动作。让我们看看实际中的top命令。 # top Top命令举例 有关Top命令更多的例子,请阅读 :Linux下12个使用Top命令的例子。 2. VmStat – 虚拟内存统计 Linux 的 VmStat 命令用于显示虚拟内存、内核线程、磁盘、系统进程、I/O 块、中断、CPU 活动 等的统计信息。缺省情况下, vmstat 命令在 Linux 系统下不可用,你需要安装一个包含了 vmstat 程序的 sysstat 软件包。命令格式的常见用法是: 1 2 3 4 | # vmstat
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
r b swpd free inact active si so bi bo in cs us sy id wa st
1 0 0 810420 97380 70628 0 0 115 4 89 79 1 6 90 3 0
|
更多的 vmstat 例子,请阅读 : 6 Linux 下的 Vmstat 命令实例 3.Lsof-列出打开的文件 在许多Linux或者类Unix系统里都有lsof命令,它常用于以列表的形式显示所有打开的文件和进程。打开的文件包括磁盘文件、网络套接字、管道、设备和进程。使用这条命令的主要情形之一就是在无法挂载磁盘和显示正在使用或者打开某个文件的错误信息的时候。使用这条命令,你可以很容易地看到正在使用哪个文件。这条命令最常用的格式如下: 1 2 3 4 5 6 7 8 9 10 11 | # lsof
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
init 1 root cwd DIR 104,2 4096 2 /
init 1 root rtd DIR 104,2 4096 2 /
init 1 root txt REG 104,2 38652 17710339 /sbin/init
init 1 root mem REG 104,2 129900 196453 /lib/ld-2.5.so
init 1 root mem REG 104,2 1693812 196454 /lib/libc-2.5.so
init 1 root mem REG 104,2 20668 196479 /lib/libdl-2.5.so
init 1 root mem REG 104,2 245376 196419 /lib/libsepol.so.1
init 1 root mem REG 104,2 93508 196431 /lib/libselinux.so.1
init 1 root 10u FIFO 0,17 953 /dev/initctl
|
有关lsof命令的用法和例子的更多信息,请参考: Linux下10个使用lsof命令的例子。 4.Tcpdump-网络包分析器 Tcpdump是最广泛使用的网络包分析器或者包监控程序之一,它用于捕捉或者过滤网络上指定接口上接收或者传输的TCP/IP包。它还有一个选项用于把捕捉到的包保存到文件里,以便以后进行分析。在几乎所有主要的Linux发布里,tcpdump都可以使用。 1 2 3 4 5 | # tcpdump -i eth0tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
22:08:59.617628 IP tecmint.com.ssh > 115.113.134.3.static-mumbai.vsnl.net.in.28472: P 2532133365:2532133481(116) ack 3561562349 win 9648
22:09:07.653466 IP tecmint.com.ssh > 115.113.134.3.static-mumbai.vsnl.net.in.28472: P 116:232(116) ack 1 win 9648
22:08:59.617916 IP 115.113.134.3.static-mumbai.vsnl.net.in.28472 > tecmint.com.ssh: . ack 116 win 64347
|
要想获得更多有关tcpdump用法的信息,请参阅: Linux下12个使用Tcpdump命令的例子。 5.Netstat-网络状态统计 Netstat是一个用于监控进出网络的包和网络接口统计的命令行工具。它是一个非常有用的工具,系统管理员可以用来监控网络性能,定位并解决网络相关问题。 1 2 3 4 5 6 7 8 9 10 11 12 13 | # netstat -a | moreActive Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 *:mysql *:* LISTEN
tcp 0 0 *:sunrpc *:* LISTEN
tcp 0 0 *:realm-rusd *:* LISTEN
tcp 0 0 *:ftp *:* LISTEN
tcp 0 0 localhost.localdomain:ipp *:* LISTEN
tcp 0 0 localhost.localdomain:smtp *:* LISTEN
tcp 0 0 localhost.localdomain:smtp localhost.localdomain:42709 TIME_WAIT
tcp 0 0 localhost.localdomain:smtp localhost.localdomain:42710 TIME_WAIT
tcp 0 0 *:http *:* LISTEN
tcp 0 0 *:ssh *:* LISTEN
tcp 0 0 *:https *:* LISTEN
|
有关Netstat更多的例子,请参阅: Linux下20个使用Netstat命令的例子。 6. Htop – Linux进程监控 Htop 是一个非常高级的交互式的实时linux进程监控工具。 它和top命令十分相似,但是它具有更丰富的特性,例如用户可以友好地管理进程,快捷键,垂直和水平方式显示进程等等。 Htop是一个第三方工具,它不包含在linux系统中,你需要使用YUM包管理工具去安装它。 关于安装的更多信息,请阅读下文. # htop Htop 命令示例截图 对于Htop的安装,请读 : 在Linux安装Htop(Linux进程监控) 7.Iotop-监控Linux磁盘I/O Iotop命令同样也非常类似于top命令和Htop程序,不过它具有监控并显示实时磁盘I/O和进程的统计功能。在查找具体进程和大量使用磁盘读写进程的时候,这个工具就非常有用。 # iotop Iotop命令举例的截图 有关如何安装和使用iotop的信息,请阅读: 在Linux下安装Iotop。 8.Iostat-输入/输出统计 Iostat是一个用于收集显示系统存储设备输入和输出状态统计的简单工具。这个工具常常用来追踪存储设备的性能问题,其中存储设备包括设备、本地磁盘,以及诸如使用NFS等的远端磁盘。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # iostat
Linux 2.6.18-238.9.1.el5 (tecmint.com) 09/13/2012
avg-cpu: %user %nice %system %iowait %steal %idle
2.60 3.65 1.04 4.29 0.00 88.42
Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
cciss/c0d0 17.79 545.80 256.52 855159769 401914750
cciss/c0d0p1 0.00 0.00 0.00 5459 3518
cciss/c0d0p2 16.45 533.97 245.18 836631746 384153384
cciss/c0d0p3 0.63 5.58 3.97 8737650 6215544
cciss/c0d0p4 0.00 0.00 0.00 8 0
cciss/c0d0p5 0.63 3.79 5.03 5936778 7882528
cciss/c0d0p6 0.08 2.46 2.34 3847771 3659776
|
有关iostat用法和举例的更多信息,请访问: Linux下6个使用iostat命令的例子。 9.IPTraf-实时局域网IP监控 IPTraf是一个在Linux控制台运行的、开放源代码的实时网络(局域网)监控应用。它采集了大量信息,比如通过网络的IP流量监控,包括TCP标记、ICMP详细信息、TCP/UDP流量分离、TCP连接包和字节数。同时还采集有关接口状态的常见信息和详细信息:TCP、UDP、IP、ICMP、非IP,IP校验和错误,接口活动等。 IP流量监控 有关IPTraf工具用法以及其他更多信息,请访问: IPTraf网络监控工具。 10. psacct 或者 acct – 监视用户活动 psacct或者acct工具用于监视系统里每个用户的活动状况。这两个服务进程运行在后台,它们对系统上运行的每个用户的所有活动进行近距离监视,同时还监视这些活动所使用的资源情况。 系统管理员可以使用这两个工具跟踪每个用户的活动,比如用户正在做什么,他们提交了那些命令,他们使用了多少资源,他们在系统上持续了多长时间等等。 有关这些命令的安装和用法举例信息,请参阅文章:使用psacct或者acct监视用户活动。 11.Monit – Linux进程和服务监控工具 Monit是一个免费的开源软件,也是一个基于网络的进程监控工具。它能自动监控和管理系统进程,程序,文件,文件夹,权限,总和验证码和文件系统。 这个软件能监控像Apache, MySQL, Mail, FTP, ProFTP, Nginx, SSH这样的服务。你可以通过命令行或者这个软件提供的网络借口来查看系统状态。 Monit Linux系统监控 更多内容请参阅:用Monit监控Linux进程 12.NetHogs-监视每个进程使用的网络带宽 NetHogs是一个开放源源代码的很小程序(与Linux下的top命令很相似),它密切监视着系统上每个进程的网络活动。同时还追踪着每个程序或者应用所使用的实时网络带宽。 NetHogs:Linux下的带宽监视 更多信息请参阅: 使用NetHogs监视Linux的网络带宽使用状况。 13.iftop-监视网络带宽 iftop是另一个在控制台运行的开放源代码系统监控应用,它显示了系统上通过网络接口的应用网络带宽使用(源主机或者目的主机)的列表,这个列表定期更新。iftop用于监视网络的使用情况,而‘top’用于监视CPU的使用情况。iftop是‘top’工具系列中的一员,它用于监视所选接口,并显示两个主机间当前网络带宽的使用情况。 iftop-监视网络带宽。 更多信息请参阅:iftop-监视网络带宽的使用情况。 14 Monitorix-系统和网络监控 Monitorix 是一个免费的轻量级应用工具,它的设计初衷是运行和监控Linux/Unix服务器系统和资源等。它有一个HTTP 网络服务器,这个服务器有规律的收集系统和网络的信息并以图形化的形式展示出来。它监控系统的平均负载和使用,内存分配、磁盘健康状况、系统服务、网络端 口、邮件统计(Sendmail,Postfix,Dovecot等),MySQL统计,等等。它就是用来监控系统的总体性能,帮助发现失误、瓶颈和异常 活动的。 15. Arpwatch – 以太网活动监视器 Arpwatch被设计用来监控Linux上的以太网地址解析 (MAC和IP地址的变化)。他在一段时间内持续监控以太网活动并输出IP和MAC地址配对变动的日志。它还可以向管理员发送邮件通知,对地址配对的增改发出警告。这对于检测网络上的ARP攻击很有用。 更多信息请参阅 : Arpwatch to Monitor Ethernet Activity 16. Suricata – 网络安全监控 Suricata 是一个开源的高性能网络安全、入侵检测和反监测工具,可以运行Linux、FreeBSD和Windows上。非营利组织OISF (Open Information Security Foundation)开发并拥有其版权。 更多信息请参阅 : Suricata – A Network Intrusion Detection and Prevention System 17. VnStat PHP – 网络流量监控 VnStat PHP 是流行网络工具”vnstat”的基于web的前端呈现。VnStat PHP 将网络使用情况呈现在漂亮的图形界面中。他可以显示以小时、日、月计的上传和下载流量并输出总结报告。 更多信息请参阅 : VnStat PHP – Monitoring Network Bandwidth 18. Nagios – 网络/服务器监控 Nagios是领先而强大的开源监控系统,他可以让网络/系统管理员在问题影响到正常的业务之前发现并解决它们。有了Nagios系统,管理员可以 在单个窗口内远程检测Linux、Windows、开关、路由器和打印机。它可以危险警告并指出系统/服务器是否有异常,这可以间接帮助你在问题发生之前 采取抢救措施。 更多信息请参阅 : Install Nagios Monitoring System to Monitor Remote Linux/Windows Hosts 我们想知道:你在用什么监控程序来监控Linux服务器的性能呢?如果我们在上面错过了你认为重要的工具,请在评论中告诉我们,不要忘了分享它!
#意识 ASAP (As Soon As Possible)原则 当线上出现诡异问题, 当你意识到靠现有的日志无法定位问题时, 当现象难以在你的开发环境重现时, 请不要执著于枯坐肉眼看代码,因为:一)不一定是你代码逻辑问题,可能是脏数据造成的,是老业务数据造成的,是分布式环境造成的,是其他子系统造成的;二)线上业务处于不稳定中,条件不允许问题定位无限期。 此时,请立即在问题相关的调用链条上,一次性: - 在函数的入口和出口打印日志,同时打印输入、输出参数
- catch(){……}里打印stacktrace,同时打印try块中关键变量的值(避免你发现某个异常是问题第一原因,却不知道是什么变量传入导致的)
- 与其他模块交互的接口入口处打印输入参数,
即, 解决线上问题归根结底要靠log、a lot of log output! 在logging的力度上切勿犹犹豫豫,我们的工程师习惯于吝啬地找两个函数打印日志、打包部署一把、没看出来、再找几个函数打印、再部署、等着现象重现再观察、……,一来二去时间流逝,闲庭信步,从客服知道的小事故变成了全国皆知的大事故。 所以,再强调一遍:在你的调用链条上,逐层调用的函数入口和出口都打印详细日志,不怕多只怕少,然后部署,等待现象重现,毕其功于一役! 通过它可以跟踪为什么系统响应变慢或者太快 - 处理完一个incoming request所耗费的时间,精确到毫秒
- 执行数据库查询的时间
- 从磁盘或者存储介质获取数据的时间
- 等等
2)异常和堆栈跟踪 3)Sessions 知道一个问题是由谁引起的非常重要,因此在日志中使用会话标识符就变得必不可少。它可以简单到是一个 IP 地址或者是一个更复杂的 UUID,只要能区分不同的请求者就足够。 4)版本号 #工具 推荐的Java Logging框架 1)log4j:我们的配置是,log4j.appender.CONSOLE.layout.ConversionPattern= [%-d{yyyy-MM-dd HH\:mm\:ss.SSS}] [%p] [%c] [%m]%n;%p是日志优先级,%c是类目名,%m是输出信息,%n是回车换行符。 2)logback:log4j创建人Ceki Gülcü后续推出了SLF4J+logback。SLF4J(Simple Logging Facade for Java)作为commons-logging的替代,为各种logging APIs提供了一个简单的统一接口,使得最终用户能够在部署的时候配置所希望的logging APIs的实现。logback胜在性能,据称“某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在logback 中需要3纳秒,而在 log4j 中则需要30纳 秒。 logback 创建记录器(logger)的速度也更快:13毫秒,而在 log4j 中需要23毫秒。更重要的是,它获取已存在的记录器只需94纳秒, 而 log4j 需要2234纳秒,时间减少到了1/23。跟java.util.logging(JUL)相比性能提高也是显著的”。 #配置 不要随便从网上找一个log4j的配置文件,请确认你理解每一个配置项 我们既然输出日志,自然期望在面对“这个问题是否从过去几天开始出现?”这样的疑问时,不至于发现你的rollingPolicy错误设置导致只能看到最近几小时的日志,或者日志发生时间没有精确到毫秒。 #理念 可用grep抽取的日志:独立的行! 我们总是希望能用grep处理日志文件。这意味着:一个日志条目永远不应该跨多行,除非你是堆栈打印。 我们会用grep问日志什么问题呢?如: - 用手机号13910******下单的顾客最近三天内都来自于哪些IP?
- 浏览地址是****?from=kfapi的顾客,但referral却是搜索引擎域名,最近三天有多少次?
- 最近一周内,订单中心执行的所有事务,耗时最长的一次是多长时间?
- ××××的接口是否真的于18:00发送了一个请求,我们收到的参数是什么?
确保你的日志能回答这样的问题。 不同关注领域写不同的日志文件 当访问和调用极其频繁,有时候你会发现把你的工程里什么信息都打印到一个日志文件里,会让你看得头昏脑胀。 最简单的示范就是Apache的访问日志和错误日志是分开的。 同样,你也可以把更加安静的事件(偶尔出现)与更加喧闹的事件分开存储。 如,对外的开放平台可以打印三种日志文件:connection log(建立链接和关闭链接,附带接入参数),message log(内部调用链),stacktrace log(异常的堆栈打印)。 #具体实现 至少精确到毫秒 日志必须包含时间戳,精确到至少毫秒级。 如果只是记录到秒级,我们曾明知代码因缺乏并发控制而产生BUG,却只能郁闷地看着精确到秒级的日志。 对Java来说,最好配置为:yyyy-MM-dd/HH:mm:ss.SSS。 请尽可能打印明确的会话标识 日志条目里打印一个会话标识(A certain session identifier),当有许多并发请求打过来时,你就能基于此字段过滤 client 了。比如,我们日志会补充打印一个浏览器 cookies 里种下的 UUID 。 log4j的isDebugEnabled判断 如果打印信息是常量字符串或简单字符串拼接,那么不需要if ( log.isDebugEnabled() )。 如果你拼装的动作比较耗资源,请用if ( log.isDebugEnabled() )。 如有可能,请将性能数据标准化输出 这样更方便grep或hadoop做性能数据抽取和挖掘,从而能很轻松地转换为图形监控。 比如,订单中心的性能数据格式为:树枝标志 当前节点起始时间 [当前节点持续时间, 当前节点自身消耗时间, 在父节点中所占的时间比例] 哪些位置需要部署性能检测点 (1)访问数据库的dao层; (2)访问外部资源的ext层; (3)访问mq的方法; (4)等等,一切不在你自己负责的工程掌握的部分(外部),或一切你认为自己工程的性能危险点,都需要加入性能监控日志。 #Sample 打印了应用的版本号,客户端的会话标识,关键步骤的执行时长。 一个好的堆栈跟踪日志
摘要: 前面我们介绍了Java当中多个线程抢占一个共享资源的问题。但不论是同步还是重入锁,都不能实实在在的解决资源紧缺的情况,这些方案只是靠制定规则来约束线程的行为,让它们不再拼命的争抢,而不是真正从实质上解决他们对资源的需求。
在JDK 1.2当中,引入了java.lang.ThreadLocal。它为我们提供了一种全新的思路来解决线程并发的问题。但是他的名字难免让我们望文生义:本地线程?
... 阅读全文
摘要: Java监视器支持两种线程:互斥和协作。
前面我们介绍了采用对象锁和重入锁来实现的互斥。这一篇中,我们来看一看线程的协作。
举个例子:有一家汉堡店举办吃汉堡比赛,决赛时有3个顾客来吃,3个厨师来做,一个服务员负责协调汉堡的数量。为了避免浪费,制作好的汉堡被放进一个能装有10个汉堡的长条状容器中,按照先进先出的原则取汉堡。如果容器被装满,则厨师停止做汉堡,如果顾客发现容器内的汉堡吃完了,... 阅读全文
原文出自 http://blog.csdn.net/woshichenxu/archive/2006/01/22/586361.aspx
1. 关于hibernate缓
存的问题:
1.1.1. 基本的缓存原理
Hibernate缓存分为二级,第一级存放于session中称为一级缓存,默认带有且不能卸载。
第二级是由sessionFactory控制的进程级缓存。是全局共享的缓存,凡是会调用二级缓存的查
询方法都会从中受益。只有经正确的配置后二级缓存才会发挥作用。同时
在进行条件查询时必须使用相应的方法才能从缓存中获取数据。比如Query.iterate()方法、load、get方法等。必须注意的是session.find方法永远是从数据库中获取数据,不会从二级缓存中获取数据,即
便其中有其所需要的数据也是如此。
查询时使用缓存的实现过程为:首先查询一级缓存中是否具有需要
的数据,如果没有,查询二级缓存,如果二级缓存中也没有,此时再执行查询数据库的工作。要注意的是:此3种方式的查询速度是依次降低的。
1.2. 存在的问题
1.2.1. 一级缓存的问题以及使用二级缓存
的原因
因为Session的生命期往往很短,存在于Session内部的第一级最快缓存的生命期当然也很短,所以第一级缓存的命中率是很低的。其对系统性能的改善也是很有限的。当然,这个Session内部缓存的主要作用是保持Session内部数据状态同步。并非是hibernate为了大幅提高系统性能所提供的。
为了提高使用hibernate的性能,除了常规的一些需要注意的方法比如:
使用延迟加载、迫切外连接、查询过滤等以外,还需要配置hibernate的二级缓存。其对系统整体性能的改善往往具有立竿见影的效果!
(经过自己以前作项目的经验,一般会有3~4倍的性能提高)
1.2.2. N+1次
查询的问题
执行条件查询时,iterate()方法具有著名的“n+1”次查询的问题,也就是说在第一
次查询时iterate方法会执行满足条件的查询结果数再加一次(n+1)的查询。但是此问题只存在于第一次查询时,在后面执行相同查
询时性能会得到极大的改善。此方法适合于查询数据量较大的业务数据。
但是注意:当数据量特别大时(比如流水线数据等)需要针对此持
久化对象配置其具体的缓存策略,比如设置其存在于缓存中的最大记录数、缓存存在的时间等参数,以避
免系统将大量的数据同时装载入内存中引起内存资源的迅速耗尽,反而降低系统的性能!!!
1.3. 使用hibernate二级缓存的其他注意
事项:
1.3.1. 关于数据的有效性
另外,hibernate会自行维护二级缓存中的数据,以保证缓存中的数据和数据库中的真实数据的一致性!无论何时,当你调用save()、update()或 saveOrUpdate()方法传递一个对象时,或使用load()、 get()、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。 当随后flush()方法被调用时,对象的状态会和数据库取得同步。
也就是说删除、更新、增加数据的时候,同时更新缓存。当然这也
包括二级缓存!
只要是调用hibernate API执行数据库相关的工作。hibernate都会为你自动保证缓存数据的有效性!!
但是,如果你使用了JDBC绕过hibernate直接执行对数据库的操作。此时,Hibernate不会/也不可能自行感知到数据库被进行的变化改动,也就不能再保证缓
存中数据的有效性!!
这也是所有的ORM产品共同具有的问题。幸运的是,Hibernate为我们暴露了Cache的清除方法,这给我们提供了一个手动保证数据有效性的机会!!
一级缓存,二级缓存都有相应的清除方法。
其中二级缓存提供的清除方法为:
按对象class清空缓存
按对象class和对象的主键id清空缓存
清空对象的集合中的缓存数据等。
1.3.2. 适合使用的情况
并非所有的情况都适合于使用二级缓存,需要根据具体情况来决
定。同时可以针对某一个持久化对象配置其具体的缓存策略。
适合于使用二级缓存的情况:
1、数据不会被第三方修改;
一般情况下,会被hibernate以外修改的数据最好不要配置二级缓存,以免引起不一致的数据。
但是如果此数据因为性能的原因需要被缓存,同时又有可能被第3方比如SQL修改,也可以为其配置二级缓存。只是此时需要在sql执行修改后手动调用cache的清除方法。以保证数据的一致性
2、数据大小在可接收范围之内;
如果数据表数据量特别巨大,此时不适合于二级缓存。原因是缓存的数据量过大可能会引起内存资源紧张,反而降低性能。
如果数据表数据量特别巨大,但是经常使用的往往只是较新的那部
分数据。此时,也可为其配置二级缓存。但是必须单独配置其持久化类的缓存策略,比如最大缓存数、缓存过期时间等,将这些参数降低至一个合理的范围(太高会
引起内存资源紧张,太低了缓存的意义不大)。
3、数据更新频率低;
对于数据更新频率过高的数据,频繁同步缓存中数据的代价可能和查询缓存中的数据从中获得的好处相当,坏处益处相抵消。此时缓
存的意义也不大。
4、非关键数据(不是财务数据等)
财务数据等是非常重要的数据,绝对不允许出现或使用无效的数据,所以此时为了安全起见最好不要使用二级缓存。
因为此时“正确性”的重要性远远大于“高性能”的重要性。
2. 目前系统中使用hibernate缓
存的建议
1.4. 目前情况
一般系统中有三种情况会绕开hibernate执行数据库操作:
此种情况使用hibernate二级缓存会不可避免的造成数据不一致的问题,
详细的设计。比如在设计上
避免对同一数据表的同时的写入操作,
使用数据库各种级别的锁定机制等。
2、动态表相关
所谓“动态表”是指在系统运行时根据用户的操作系统自动建
立的数据表。
比如“自定义表单”等属于用户自定义扩展开发性质的功能模块,因为此时数据表是运行时建立的,所以不能进行hibernate的映射。因此对它的操作只能
是绕开hibernate的直接数据库JDBC操作。
如果此时动态表中的数据没有设计缓存,就不存在数据不一致的问
题。
如果此时自行设计了缓存机制,则调用自己的缓存同步方法即
可。
3、使用sql对
hibernate持久化对象表进行批量删除时
此时执行批量删除后,缓存中会存在已被删除的数据。
分
析:
当执行了第3条(sql批量删除)后,后续的查询只可能是以下三种方式:
a.
session.find()方法:
根据前面的总结,find方法不会查询二级缓存的数据,而是直接查询数据库。
所以不存在数据有效性的问题。
b. 调用iterate方法执行条件查询时:
根据iterate查询方法的执行方式,其每次都会到数据库中查询满足条件的id值,然后再根据此id 到缓存中获取数据,当缓存中没有此id的数据才会执行数据库查询;
如果此记录已被sql直接删除,则iterate在执行id查询时不会将此id查询出来。所以,即便缓存中有此条记录也不会被客户获得,也就
不存在不一致的情况。(此情况经过测试验证)
c. 用get或load方法按id执行查询:
客观上此时会查询得到已过期的数据。但是又因为系统中执行sql批量删除一般是
针对中间关联数据表,对于
中间关联表的查询一般都是采用条件查询 ,按id来查询某一条关联关系的几率很低,所以此问题也不存在!
如果某个值对象确实需要按id查询一条关联关系,同时又因为数据量大使用了sql执行批量删除。当满足此两个条件时,为了保证按id 的查询得到正确的结果,可以使用手动清楚二级缓存中此对象的数据的方法!!
(此种情况出现的可能性较小)
1.5. 建议
1、建议不要使用sql直接执行数据持久化对象的数据的更新,但是可以执行批量删除。(系统中需要批量更新的地方也较少)
2、如果必须使用sql执行数据的更新,必须清空此对象的缓存数据。调用
SessionFactory.evict(class)
SessionFactory.evict(class,id)
等方法。
3、在批量删除数据量不大的时候可以直接采用hibernate的批量删除,这样就不存在绕开hibernate执行sql产生的缓存数据一致性的问题。
4、不推荐采用hibernate的批量删除方法来删除大批量的记录数据。
原因是hibernate的批量删除会执行1条查询语句外加满足条件的n条删除语句。而不是一次执行一条条件删除语句!!
当待删除的数据很多时会有很大的性能瓶颈!!!如果批量删除数
据量较大,比如超过50条,可以采用JDBC直接删除。这样作的好处是只执行一条sql删除语句,性能会有很大的改善。同时,缓存数据同步的问题,可以采用
hibernate清
除二级缓存中的相关数据的方法。
调用 SessionFactory.evict(class) ;SessionFactory.evict(class,id)等方法。
所以说,对于一般的应用系统开发而言(不涉及到集群,分布式数
据同步问题等),因为只在中间关联表执行批量删除时调用了sql执行,同时中间关联表一般是执行条件查询不太可能执行按id查询。所以,此时可以直接执行sql删除,甚至不需要调用缓存的清除方法。这样做不会导致以后配置
了二级缓存引起数据有效性的问题。
退一步说,即使以后真的调用了按id查询中间表对象的方法,也可以通过调用清除缓存的方法来解决。
4、具体的配置方法
根据我了解的很多hibernate的使用者在调用其相应方法时都迷信的相信“hibernate会自行为我们处理性能的问题”,或
者“hibernate会自动为我们的所有操作调用缓存”,实际的情况是hibernate虽然为我们提供了很好的缓存机制和扩展缓存框架的支持,但是必
须经过正确的调用其才有可能发挥作用!!所以造成很多使用hibernate的系统的性能问题,实际上并不是hibernate不行或者不好,而是因为使
用者没有正确的了解其使用方法造成的。相反,如果配置得当hibernate的性能表现会让你有相当“惊喜的”发现。下面我讲解具体的配置方法.
ibernate提供了二级缓存的接口:
net.sf.hibernate.cache.Provider,
同
时提供了一个默认的 实现net.sf.hibernate.cache.HashtableCacheProvider,
也可以配置
其他的实现 比如ehcache,jbosscache等。
具体的配置位置位于hibernate.cfg.xml文件中
<property
name="hibernate.cache.use_query_cache">true</property>
<property
name="hibernate.cache.provider_class">net.sf.hibernate.cache.HashtableCacheProvider</property>
很多的hibernate使用者在 配置到 这一步 就以为 完事了,
注意:其实光这样配,根本
就没有使用hibernate的二级缓存。同时因为他们在使用hibernate时大多时候是马上关闭session,所以,一级缓存也没有起到任何作
用。结果就是没有使用任何缓存,所有的hibernate操作都是直接操作的数据库!!性能可以想见。
正确的办法是除了以上的配置外还应该配置每一个vo对象的具体缓存策略,在影射文件中配置。例如:
<hibernate-mapping>
<class
name="com.sobey.sbm.model.entitySystem.vo.DataTypeVO"
table="dcm_datatype">
<cache usage="read-write"/>
<id
name="id" column="TYPEID" type="java.lang.Long">
<generator
class="sequence"/>
</id>
<property name="name" column="NAME"
type="java.lang.String"/>
<property name="dbType"
column="DBTYPE" type="java.lang.String"/>
</class>
</hibernate-mapping>
关键就是这个<cache usage="read-write"/>,其有几个选择
read-
only,read-write,transactional,等
然后在执行查询时 注意了
,如果是条件查询,或者返回所有结果的查询,此时session.find()方法
不会获取缓存中的数据。只有调用query.iterate()方法时才会调缓存的数据。
同时 get 和 load方法 是都会查询缓存中的数据 .
对于不同的缓存框架具体的配置方法会有不同,但是大体是以上的配置
(另外,对于支持事务型,以及支持集群的环境的配置我会争取在后续的文章中中 发表出来)
3. 总结
总之是根据不同的业务情况和项目情况
对hibernate进行有效的配置和正确的使用,扬长避短。不存在适合于任何情况的一个“万能”的方案。
以上结论及建议均建立在自己在对 Hibernate 2.1.2中的测试结果以及以前的项目经验的基础上。如有谬处,请打家提
出指正:)!
最后,祝大家
新年快乐!!在新的一年里 取得人生的进步!!!
创建项目:
打开NetBeans 6.5.1,选择文件—》新建项目,选择Java Web,然后在项目列表中选择 Web 应用程序,下一步
选择使用专用文件夹存储库,指定库文件夹的位置,通常是默认的.\lib,即项目文件夹下的lib文件夹,下一步
选择Web应用服务器。这里选择Tomcat 6.0.18 ,Java EE版本选择 Java EE 5,下一步
在框架对话框中什么都不选择,直接点击完成。
配置项目:
1. 配置OperaMask
在WEB-INF文件夹下新建一个faces-config.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd" version="1.2">
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>
在新创建的项目中,右键单击“库”节点,选择添加库
然后选择导入,选择Spring 2.5库,选择导入库,添加库。
再次选择添加库,然后选择创建。库名称为OperaMask,库类型为类库:
在“定制库”中选择“添加JAR/文件夹”,在弹出的对话框中选择OperaMask的基本jar包,并将导入方式指定为:复制到库文件夹。
一路选是。
2. 配置Spring以及与OperaMask的整合
然后再创建一个OperaMask_SpringCompatibility库,将OperaMask中的spring文件夹下的operamasks-spring.jar添加进来
然后在新创建的项目中展开WEB-INF文件夹,打开web.xml:按照OperaMask包中的blank样例程序的配置进行配置。
之后,在web.xml中配置如下内容:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
|
然后,在源包节点下创建Spring配置文件applicationContext.xml,选择如图所示的几个命名空间:
选择完成,然后在Beans节点之间添加如下内容:
<!-- 开启基于注解的配置 -->
<context:annotation-config/>
<!-- 使 AOM 中的 LiteBean 同样能够被 Spring 所感应到 -->
<bean class="org.operamasks.faces.spring.ManagedBeanConfigurer"/>
|
3. 配置JPA持久化支持
在服务选项卡中,选择MySQL数据库驱动,然后右键单击,选择连接设置,配置你要使用的数据库:
数据库配置完毕,然后回到项目选项卡,右键单击刚创建的项目,选择新建-->其它-->持久性-->持久性单元
在新建持久性单元对话框中选择持久性库为Hibernate,即选择Hibernate为JPA持久单元的实现,数据库连接选择我们刚配置好的MySQL连接。
单击完成。此时,Hibernate的JPA库已经被添加到项目的lib目录下了。包含了基本的Hibernate jar包和Hibernate JPA支持jar包。
注:使用JPA的一个好处就是我们不需要在一个统一的配置文件里罗列所有的实体类,而是可以让实体管理器自动扫描所有被@Entity注解了的实体类。要实现这种功能,如果项目的JPA实现迁移到TopLink Essential,需要加入下面的配置:
找到项目中的“配置文件”节点,打开persistence.xml文件,调整到XML视图中,在<provider> </provider>节点后添加:
<exclude-unlisted-classes>false</exclude-unlisted-classes> 一行。如果希望使用Hibernate实现,请一定不要加入这一行,否则,您必须将您创建的所有实体类逐一添加到persistence.xml中。
如果要使用Spring提供的JpaTemplate(即实现JpaDaoSupport方式),则回到spring的applicationContext.xml文件,在<beans>节点里面添加:
<!-- 利用Spring的实体管理器工厂来创建JPA实体管理器,传入的参数为persistence.xml中指定的持久化单元名称 -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="DMCSv1PU"/>
</bean>
<!-- 声明一个Spring提供的JPA事务管理器,传入的参数正是Spring中的实体管理器工厂 -->
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- 开启Spring提供的基于注解的声明式事务管理 -->
<tx:annotation-driven transaction-manager="txManager"/>
|
至此,持久化支持配置完毕。
创建实体类和相应的JPA控制类
右键单击项目,选择新建-->其它-->持久性-->通过数据库生成实体类,选择数据库表:
点击下一步,输入合适的包名
下一步,映射选项如下图所示:
选择新建-->其它-->持久性-->基于实体类的JPA控制器类,下一步:
添加要生成控制器类的实体类,下一步:
选择合适的包,然后完成。
后面的配置无非就是将JPA的控制器类写入Spring的配置文件,然后在AOM的LiteBean中注入这些控制器类,实现数据库操作以及相应的业务逻辑。
注:解决Spring与Hibernate JPA的冲突:
如上图所示:选择工具--> 库 --> 库位置-->选择当前编辑的项目,选中Spring Framework 2.5,然后去掉cglib2.2那个jar包。这个包与Hibernate JPA中的cglib 2.1.3.jar有冲突
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!776.entry
今天发现服务器上的Oracle11g突然登录不上去了,提示ORA-28002错误,说是口令过期。
不当DBA还真不知道Oracle有这神秘功能。
上网上一查,有类似遭遇的朋友在论坛上求助,人家让他找DBA。汗。。。
好在有的DBA乐于分享,终于找到了解决方案:
1. 用DBA账户登录SQL PLUS。我用的是sysman。
2. 系统会提示口令失效,但是会马上让你重置新密码。
3. 重置之后,进入SQL PLUS控制台。
4. 查看口令失效用户的profile文件
SQL>SELECT username,profile FROM dba_users;
EM(Web界面的控制台):服务器>用户,查看口令失效的用户对应的概要文件,这里假设为DEFAULT,下同。
5.
查看对应的概要文件的口令有效期设置
SQL>SELECT * FROM dba_profiles WHERE profile='DEFAULT' AND resource_name='PASSWORD_LIFE_TIME';
EM(Web界面的控制台):服务
器>概要文件>选择刚刚查到的概要文件DEFAULT>查看,查看口令下面的有效期值。
6.将口令有效期默认值180天
修改成“无限制”(此项要慎重!除非你真得不想要这个密码失效的机制!)
SQL>ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;
EM:服务器>概要文件>选择刚刚查到的概要文件DEFAULT>编辑>口令,在有效期输入
或选择你需要的值,保存。
该参数修改实时生效。
出于数据库安全性考虑,不建议将PASSWORD_LIFE_TIME值设置
成UNLIMITED,即建议客户能够定期修改数据库用户口令。
在修改PASSWORD_LIFE_TIME值之前已经失效的用户,还是需
要重新修改一次密码才能使用。
SQL>ALTER USER test INDENTIFIED BYpassword
也可以从SQL Developer 里面来修改用户的密码,用sysman账户登录以后,找到数据库中的其他用户节点,展开,找到你要修改密码的用户。然后编辑用户,对用户密码进行重置,如下图:
@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
内部服务参数配置:
JAVA_OPTS="-server -XX:+UseParNewGC -Xms1024m -Xmx2048m -XX:MaxNewSize=128m -XX:NewSize=128m -XX:PermSize=96m -XX:MaxPermSize=128m -XX:+UseConcMarkSweepGC -XX:+CMSPermGenSweepingEnabled -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:CMSInitiatingOccupancyFraction=1 -XX:+CMSIncrementalMode -XX:MaxTenuringThreshold=0 -XX:SurvivorRatio=20000 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:CMSIncrementalDutyCycleMin=10 -XX:CMSIncrementalDutyCycle=30 -XX:CMSMarkStackSize=8M -XX:CMSMarkStackSizeMax=32M"
前端应用参数配置:
JAVA_OPTS="-server -Xmx4096m -Xms4096m -Xmn480m -Xss256k -XX:PermSize=128m -XX:MaxPermSize=256m -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=8 -XX:CMSFullGCsBeforeCompaction=0
-XX:+UseCMSCompactAtFullCollection -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=7 -XX:GCTimeRatio=19
-Xnoclassgc -XX:+DisableExplicitGC -XX:+UseParNewGC -XX:-CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=70 -XX:SoftRefLRUPolicyMSPerMB=0"
参数说明:
-Xmx1280m:设置JVM最大可用内存为1280m。最大可设为3550m。具体应用可适当调整。
-Xms1280m:设置JVM初始内存为1280m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn480m:设置年轻代大小为480m。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss256k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:PermSize=64m:指定 jvm 中 Perm Generation 的最小值。 这个参数需要看你的实际情况。可以通过jmap 命令看看到底需要多少。
-XX:MaxPermSize=128m:指定 Perm Generation 的最大值
-XX:+UseConcMarkSweepGC:设置并发收集器
-XX:ParallelGCThreads=8:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
-XX:CMSFullGCsBeforeCompaction=0:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片。
-XX:SurvivorRatio=8:每个survivor space 和 eden之间的比例。
-XX:MaxTenuringThreshold=7:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。
-XX:GCTimeRatio=19:设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)。
-Xnoclassgc:禁用类垃圾回收,性能会有一定提高。
-XX:+DisableExplicitGC:当此参数打开时,在程序中调用System.gc()将会不起作用。默认是off。
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。
-XX:-CMSParallelRemarkEnabled:在使用 UseParNewGC 的情况下 , 尽量减少 mark 的时间。
-XX:CMSInitiatingOccupancyFraction=70:指示在 old generation 在使用了 70% 的比例后 , 启动 concurrent collector。
-XX:SoftRefLRUPolicyMSPerMB=0:每兆堆空闲空间中SoftReference的存活时间。
@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
首先我要感谢aa和Liu Xing帮我发现了我日志中的错误。之前比较粗心,把3条SQL语句写成一样的了,对于给读者造成的麻烦,我深表抱歉。
今天我把原文做了修订,为了对得起读者对我的关注,我重新深入的研究了这个问题,在后面,我会把来龙去脉写清楚。
问题:
语句1:
Select * from table1 A where A.col1 not in ( select col1 from table2 B )
如果这样,本来应该有一条数据,结果没有。
如果我改写成这样:
语句2:
select * from table1 A where not exists (SELECT * FROM table2 B where B.col1 = A.col1)
结果就正确,有一条数据显示。
经过一番搜索,原以为是子查询结果集太大的原因。
后来有网上强人指点:子查询里面有空集。即子查询的结果集里面有NULL的结果。
把查询语句修改成:
语句3:
Select * from table1 A where A.col1 not in ( select col1 from table2 B where B.col1 is not null )
果然就查出来了。而且一点不差。。。厉害阿~~~
下面是针对本文题的分析:
1。 首先来说说Oracle中的NULL。
Oracle中的NULL代表的是无意义,或者没有值。将NULL和其他的值进行逻辑运算,运算过程中,NULL的表现更象是FALSE。
下面请看真值表:
|
AND NULL
|
OR NULL |
TRUE |
NULL |
TRUE |
FALSE |
FALSE |
NULL |
NULL |
NULL
|
NULL |
另外,NULL和其他的值进行比较或者算术运算(<、>、=、!=、+、-、*、/),结果仍是NULL。
如果想要判定某个值是否为NULL,可以用IS NULL或者IS NOT NULL。
2. 再来说说Oracle中的IN。
in是一个成员条件, 对于给定的一个集合或者子查询,它会比较每一个成员值。
IN功能上相当于 =ANY 的操作,而NOT IN 功能上相当于 !=ALL 的操作。
IN在逻辑上实际上就是对给定的成员集合或者子查询结果集进行逐条的判定,例如:
SELECT * FROM table1 A WHERE A.col1 in (20,50,NULL);
实际上就是执行了
SELECT * FROM table1 A WHERE A.col1=20 OR A.col1=50 OR A.col1=NULL;
这样,根据NULL的运算特点和真值表,我们可以看出,上边这个WHERE 字句可以被简化(如果返回NULL则无结果集返回,这一点和FALSE是一样的)为
WHERE A.col1=20 OR A.col1=50
也就是说,如果你的table1中真的存在含有NULL值的col1列,则执行该语句,无法查询出那些值为null的记录。
再来看看NOT IN。根据逻辑运算关系,我们知道,NOT (X=Y OR N=M) 等价于 X!=Y AND N!=M,那么:
SELECT * FROM table1 A WHERE A.col1 not in (20,50,NULL)
等价于
SELECT * FROM table1 A WHERE A.col1!=20 AND A.col1!=50 AND A.col1!=NULL
根据NULL的运算特性和真值表,该语句无论前两个判定条件是否为真,其结果一定是NULL或者FALSE。故绝对没有任何记录可以返回。
这就是为什么 语句1查不到应有结果的原因。当然,如果你用NOT IN的时候,预先在子查询里把NULL去掉的话,那就没问题了,例如 语句3。
有些童鞋可能要问了:那如果我想把A表里面那些和B表一样col1列的值一样的记录都查出来,即便A、B两表里面的col1列都包括值为NULL的记录的话,用这一条语句就没办法了吗?
我只能很遗憾的告诉你,如果你想在WHERE后面单纯用IN 似乎不太可能了,当然,你可以在外部的查询语句中将NULL条件并列进去,例如:
SELECT * FROM table1 A WHERE A.col1 in (SELECT B.col1 FROM table2 B) OR A.col1 IS NULL;
3. 最后谈谈EXISTS。
有人说EXISTS的性能比IN要好。但这是很片面的。我们来看看EXISTS的执行过程:
select * from t1 where exists ( select * from t2 where t2.col1 = t1.col1 )
相当于:
for x in ( select * from t1 )
loop
if ( exists ( select * from t2 where t2.col1 = x.col1 )
then
OUTPUT THE RECORD in x
end if
end loop
也就是说,EXISTS语句实际上是通过循环外部查询的结果集,来过滤出符合子查询标准的结果集。于是外部查询的结果集数量对该语句执行性能影响最大,故如果外部查询的结果集数量庞大,用EXISTS语句的性能也不一定就会好很多。
当然,有人说NOT IN是对外部查询和子查询都做了全表扫描,如果有索引的话,还用不上索引,但是NOT EXISTS是做连接查询,所以,如果连接查询的两列都做了索引,性能会有一定的提升。
当然至于实际的查询效率,我想还是具体情况具体分析吧。
那么我们不妨来分析一下语句2为什么能够的到正确的结果吧:
语句2是这样的:
select * from table1 A where not exists (SELECT B.col1 FROM table2 B where B.col1 = A.col1)
实际上是这样的执行过程:
for x in ( select * from table1 A )
loop
if (not exists ( select * from table2 B where B.col1 = x.col1 )
then
OUTPUT THE RECORD in x
end if
end loop
由于表A中不包含NULL的记录,所以,遍历完表A,也只能挑出表A中独有的记录。
这就是为什么 语句2能够完成 语句3的任务的原因。
但如果表A中存在NULL记录而表B中不存在呢?
这个问题请大家自己分析吧。哈哈。有答案了可以给我留言哦。
答案:A表中的NULL也会被查出来。因为select * from table2 B where B.col1 = NULL不返回结果,故
not exists ( select * from table2 B where B.col1 = x.col1 )的值为真。
以上SQL运行结果在MySQL和Oracle上都已经通过。
想从备份的dmp文件中导入某些表的时候,可以用如下imp命令,格式:
imp username/password@本地net服务名 file=xxx.dmp fromuser=xx touser=xx tables=(tablename)
username:登陆数据库的用户名
password:登陆数据库的密码
本地net服务名:连接服务器的本地net服务名
file:你的dmp文件的路径
fromuser,touser:从一个用户导入到另外一个用户
tables:从dmp文件中导入的表名
一、emacs编辑器简介
emacs编辑器是由C语言和LISP语言编写的。LISP(链表处理语言)是由约翰·麦卡锡在1960年左右创造的一种基于λ演算的函数式编程语言。 我们可以使用LISP来扩展emacs,从而为emacs添加更多的命令。(补:emacs -nw:以命令行的方式来运行emacs,而不启动GUI界面)
* 自动保存功能
如果你已经修改了一个文件,但是还没来得及存盘你的计算机就罢工了,那么你所做的修改就很可能会丢失。为了避免这样的不幸发生,Emacs 会定期将正在编辑的文件写入一个“自动保存”文件中。自动保存文件的文件名的头尾各有一个“#”字符,比如你正在编辑的文件叫“hello.c”,那么它 的自动保存文件就叫“#hello.c#”。这个文件会在正常存盘之后被 Emacs 删除。
所以,假如不幸真的发生了,你大可以从容地打开原来的文件(注意不是自动保存文件)然后输入 M-x recover file<Return> 来恢复你的自动保存文件。在提示确认的时候,输入 yes<Return>。
* 其他
- 当emacs失去响应时,C-g命令可用来结束纸条命令的执行。其功能相当于Shell中的Ctrl+C
- 有一些 Emacs 命令被“禁用”了,以避免初学者在不了解其确切功能的情况下误用而造成麻烦。如果你用到了一个被禁用的命令,Emacs 会显示一个提示消息,告诉你这个命令到底是干什么的,询问你是否要继续,并在得到你的肯定之后再执行这命令
二、emacs编辑器的界面
1. 编辑区
用来进行文本编辑的区域。
2. 回显区
如果 Emacs 发现你输入多字符命令的节奏很慢,它会在窗格的下方称为“回显区”的地方给你提示。回显区位于屏幕的最下面一行。
3. 状态栏
- 位于回显区正上方的一行被称为“状态栏”。状态栏最开头的星号(*)表示你已经对文字做过改动。刚刚打开的文件肯定没有被改动过,所以状态栏上显示的不是星号而是短线(-)。
- 状态栏中的小括号用来指明当前使用的编辑模式,默认是fundamental(主模式),emacs的主模式包括了文本模式以及编辑程序源码的Lisp模式等。
三、emacs所能提供的工作环境
- emacs可以执行Shell命令
- emacs可以作为Directory Editor(Dired)
- emacs可以编辑、编译以及调试程序
- emacs可以编辑其它主机上的文档
- emacs可以打印文件
- emacs具有年历(Calendar)以及日记功能
- emacs可以用来阅读man page和info文档
- emacs可以收发电子邮件
- emacs可以阅读网络上的电子布告栏(GNUS)
- emacs具有版本控制的功能(CVS)
- emacs可以提供娱乐环境(游戏功能)
emacs所提供的这些功能,都是先唤起代表此功能的模式(mode)。emacs的模式,分成主要模式(major mode)与次要模式(minor mode)。每一次只能使用一个主模式,而且主模式是必须要的。在一个主模式下,俄可以搭配一个以上的次要模式。使用次要模式相当于启用了该次要模式所对应的插件。
四、emacs命令
- emacs中的每一个命令都有一个命令名,命令名就是该命令所对应的LISP函数的函数名。在emacs中,我们可以为这些命令配置快捷键,从而达到快速调用命令的目的。
- .使用emacs来执行命令的方法有两种:(1).使用Ctrl键 (2).使用Meta键。所有emacs命令都可以用Meta键表示出来,键盘上如果没有Meta键,则可以用Alt键或ESC键来代替。常用的emacs命令通常会有一个快捷键与之相连。快捷键通常是以Ctrl来开头(C-x C-c)。如果要使用Meta键来表达与“Ctrl-x Ctrl-c”相同的效果,则使用“M-x save-buffers-kill-emacs”。使用Meta键,可以利用emacs的completion功能。使用emacs的completion功能的方法是将部分字符串键入后,再按下TAB、SPACE或?键即可。
(1).TAB键:尽可能将其的字填满。
(2).SPACE键:将分隔符(-)之前的字填满。
(3).?:将所有可能的completion选择都列出来。
- 由于emacs中所有的命令都有一个命令名(LISP函数的函数名),因此,我们可以使用“M-x 命令名”来调用emacs中的所有的命令。
* 基本光标控制
- C-v:向下翻屏,与PageDown效果相同(v-> vertical)
- M-v:向上翻屏,与PageUp效果相同
- C-l:重绘屏幕,并将光标所在行置于屏幕的中央
- C-b:光标向前移动一格(b->backward)
- C-f:光标向后移动一格(f->forward)
- C-p:光标向前移动一行(p->previous)
- C-n:光标向后移动一行(n->next)
- M-b:光标向前移动一个单词
- M-f:光标向后移动一个单词
- C-a:光标移动到行首
- C-e:光标移动到行尾
- M-<:光标移动到文章的开头(注意:“<”的输入要shift键,实际为Alt+Shift+<)
- M->:光标移动到文章的结尾
- C-u:给命令传递参数。例如:“C-u 2 C-d”表示删除两个字符
- M-x goto-line n RET:调到第n行
* 编辑命令
- C-d:删除光标后的一个字符
- C-k:删除从光标到行尾的字符(k->killl)
- C-x u:Undo(想要redo,随便输入一个字符,在Undo)
- C-SPC、C-@、M-x set-mark-command:设置mark
- C-x h:将整个缓冲区设置为区域
- C-w:将区域的文本删除,并放入yanking ring中。区域指的是从mark到point(光标所处的位置称为point)之间的文本
- M-w:复制区域到yanking ring中
- C-y:将yanking ring中最后一个区域插入当前缓冲区
- M-j:回车并且到下一行产生适当的缩进
- M-m:将光标移动到当前行的第一个非空白字符上
- M-;:产生通用注释
- M-x comment-region:把块注释掉
- M-x kill-comment:消除注释
* 查找与替换
- C-s:向后搜索,光标将停在第一个匹配的字符串处。再按一次C-s将继续搜索下一个匹配的字符串。如果要停止搜索,则使用C-g,此时光标将会回到搜索开始的位置
- C-r:向前搜索
- M-x replace-string:替换
* 文件操作
- C-x c-f:打开文件,如果文件不存在则创建
- C-x C-s:保存文件。第一次存盘时,emacs会将文件重命名来备份。重命令的规则通常是在原文件名后加上一个“~”字符。如果要关闭emacs的自动备份功能,使用 M-x customize-variable <Return> make-backup-files <Return>
- C-x C-w:将文件“另存为”
- C-x C-v:打开一个新文件,并关闭当前缓冲区
- C-x C-r:以只读的方式打开文件
- C-x i:将文件插入光标当前位置
* 缓冲区
- Emacs 把每个编辑中的文件都放在一个称为“缓冲区(buffer)”的地方。每打开一个文件,Emacs 就在其内部开辟一个缓冲区用来保存打开的文件的数据。ESC ESC ESC命令可以用来退出打开的小缓冲区,比如:命令提示窗格等
- C-x C-b 列出当前所有的缓冲区(b->buffer)
- C-x b 缓冲区名:切换到指定的缓冲区(例如:C-x b M<tab>:切换到以M开头的缓冲区)
- C-x s:保存emacs中所有的缓冲区(s->save)
- C-x right:切换到下一个缓冲区
- C-x left:切换到前一个缓冲区
- C-x C-c:退出emacs,并询问用户是否保存
- C-x k:关闭缓冲区
- C-z:将emacs挂起,然后回到Shell中,并不退出emacs。之后,我们可以使用%emacs或fg命令来回到emacs
* 窗口
Emacs 可以有多个窗格,每个窗格显示不同的文字。
- C-x 0:关闭光标所在的窗口
- C-x 1:保留光标所在的窗格,并将其扩大到整个屏幕,同时关掉所有其它的窗格
- C-x 2:水平分割当前窗口
- C-x 3:垂直分割当前窗口
- C-x o:在emacs的窗格中进行切换(o->other)
- C-M-v:滚动下方的窗格。一般在我们使用下方的窗格进行参考,而又不想将光标切换到下一个窗格时使用
* 使用帮助
- C-h c 快捷键:显示快捷键的简要说明
- C-h k 快捷键:显示快捷键所对应的命令名及其详细说明
- C-h a 关键字:显示包含有指定关键字的命令
- C-h i:查看Info文档
* 在emacs中运行shell命令
- M-! cmd RET:打开一个名为“*Shell Command Output*“的窗口,并把该命令的执行结果显示在其中。按下”C-x 1“组合键可以关闭这个窗口。由于Shell命令的输出是在一个编辑缓冲区里,因此我们可以对它进行编辑、保存等操作。
- M-| cmd RET:运行Shell命令,并使用编辑窗口中选定的区域作为该Shell命令的输入,然后可以选择是否用该Shell命令的输出来替换编辑窗口中选中的区域。
- C-u M-! cmd RET:执行一条Shell命令,并将其输出放到编辑区中光标所在的位置处,而不将其输出到”Shell Command Output“窗口。
- M-x shell:运行一个子Shell,该子Shell对应于emacs中的一个名为”*Shell*"的缓冲区,此后,我们就可以交互式的运行Shell命令了。
- M-x term:运行一个子Shell,该子Shell对应于emacs中的一个名为“*Terminal*”的缓冲区。使用该命令获得的子Shell是一个完整的Shell的模拟,与我们直接在Shell中操作没有什么差别。
- M-x eshell:运行emacs shell。该Shell为emacs自己实现的一个shell,而前面运行的shell都为系统中的shell程序(例如:/bin/csh等)。我们可以通过设置变量shell-file-name来设置emacs所使用的默认shell
* Dired功能
- emacs的Dired(Directory Editor)功能使emacs缓冲区能够用来显示目录列表,并可以用来进入目录的子目录。Dired缓冲区是只读的,不能够被修改。
- C-x d:进入Dired
* emacs配置文件
emacs配置文件通常位于计算机的 $HOME 目录,如果是 MS Windows (Windows 2000, Windows XP以上),默认是 c:\Documents and Settings\username\Application Data\,这个适用于 Emacs22 以上的版本。Emacs21 默认的 $HOME 目录在 C:\ 。当然也可以通过环境变量 $HOME 重新设置,总之在比较大众化的操作系统中,你都可以通过C-x C-f ~/.emacs 来编辑您的个性化配置文件。
;;显示时间
(display-time)
;;显示行号
(column-number-mode t)
(show-paren-mode t)
;;设置TAB宽度为4
(setq default-tab-width 4)
;;以下设置缩进
(setq c-indent-level 4)
(setq c-continued-statement-offset 4)
(setq c-brace-offset -4)
(setq c-argdecl-indent 4)
(setq c-label-offset -4)
(setq c-basic-offset 4)
(global-set-key "\C-m" 'reindent-then-newline-and-indent)
(setq indent-tabs-mode nil)
(setq standard-indent 4)
;;开启语法高亮。
(global-font-lock-mode 1)
;;设置默认工作目录
(setq default-directory "/home/test/source/")
;; 去掉滚动条
(set-scroll-bar-mode nil)
;;关闭开启画面
(setq inhibit-startup-message t)
(setq indent-tabs-mode t)
;;不产生备份文件
(setq make-backup-files nil)
;;设置自定义变量
(custom-set-variables
'(column-number-mode t)
'(current-language-environment "UTF-8")
'(display-time-mode t)
'(ecb-options-version "2.32")
'(mouse-1-click-in-non-selected-windows t)
'(mouse-drag-copy-region t)
'(mouse-yank-at-point t)
'(save-place t nil (saveplace))
'(show-paren-mode t)
'(transient-mark-mode t))
(custom-set-faces
;;选择小工具栏图标
(tool-bar-mode -1)
rectangle(列)模式编辑 和emacs shell(摘抄)
不敢独享,与大家分享。也可以在Emacs中用C-x C-h列出全部命令,查找C-x r c,所有列模式命令都是以C-x r开始的
C-x r C-@ point-to-register
C-x r SPC point-to-register
C-x r + increment-register
C-x r b bookmark-jump
C-x r c clear-rectangle
先用C-space或者C-@设一个mark,移动光标到另一点,使用C-x r c可以清楚mark到光标处的矩形区域,该区域留下空白。
C-x r d delete-rectangle
删除矩形区域,不留空白,后面的字符前移
C-x r f frame-configuration-to-register
C-x r g insert-register
C-x r i insert-register
将某个寄存器的内容插入某处
C-x r j jump-to-register
C-x r k kill-rectangle
就是剪切某个选定的矩形区域,用C-x r y可以贴上
C-x r l bookmark-bmenu-list
C-x r m bookmark-set
C-x r n number-to-register
C-x r o open-rectangle
在选定的矩形区域插入空白
C-x r r copy-rectangle-to-register
将选定的矩形区域复制到某个寄存器
C-x r s copy-to-register
C-x r t string-rectangle
在选定区域所有列前插入同样的字符
C-x r w window-configuration-to-register
C-x r x copy-to-register
C-x r y yank-rectangle
类似于矩形区域的粘贴,就是将刚用C-x r k剪切的矩形区域粘贴过来
C-x r C-SPC point-to-register
摘自:http://chandlewei.blogbus.com/logs/15583440.html
在 Emacs 里面同时打开多个 shell 会话:
通过重命名shell所在缓冲区的名字可以开启多个shell
E-x shell
E-x rename-buffer shellA
E-x shell
这时就开启了两个不相干的shell:shellA 和*shell*
也可以通过其他方式来开启shell,例如:
1. M-x eshell 开启Emacs Shell
2. M-x term 开启linux终端,可以指定你要的终端SHELL。但是这个可能会拦截你的EMACS命令。
整理你的屏幕:
通过C-c C-o可以清除上次命令的输出,特别是对于cat或dmesg这种产生大量输出的命令。
详细文章:http://www.ibm.com/developerworks/cn/aix/library/0811_yangbh_emacs2/index.html
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!826.entry
MOZILLA PUBLIC LICENSE
MPL
License,允许免费重发布、免费修改,但要求修改后的代码版权归软件的发起者。这种授权维护了商业软件的利益,,它要求基于这种软件得修改无偿贡献
版权给该软件。这样,围绕该软件得所有代码得版权都集中在发起开发人得手中。但MPL是允许修改,无偿使用得。MPL软件对链接没有要求。
BSD开源协议(original BSD license、FreeBSD license、Original BSD license)
BSD开源协议是一个给于使用者很大自由的协议。基本上使用者可以"为所欲为",可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。
但"为所欲为"的前提当你发布使用了BSD协议的代码,或则以BSD协议代码为基础做二次开发自己的产品时,需要满足三个条件:
- 如果再发布的产品中包含源代码,则在源代码中必须带有原来代码中的BSD协议。
- 如果再发布的只是二进制类库/软件,则需要在类库/软件的文档和版权声明中包含原来代码中的BSD协议。
- 不可以用开源代码的作者/机构名字和原来产品的名字做市场推广。
BSD
代码鼓励代码共享,但需要尊重代码作者的著作权。BSD由于允许使用者修改和重新发布代码,也允许使用或在BSD代码上开发商业软件发布和销售,因此是对
商业集成很友好的协议。而很多的公司企业在选用开源产品的时候都首选BSD协议,因为可以完全控制这些第三方的代码,在必要的时候可以修改或者二次开发。
Apache Licence 2.0(Apache License, Version 2.0、Apache License, Version 1.1、Apache License, Version 1.0)
Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。需要满足的条件也和BSD类似:
- 需要给代码的用户一份Apache Licence
- 如果你修改了代码,需要再被修改的文件中说明。
- 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标,专利声明和其他原来作者规定需要包含的说明。
- 如果再发布的产品中包含一个Notice文件,则在Notice文件中需要带有Apache Licence。你可以在Notice中增加自己的许可,但不可以表现为对Apache Licence构成更改。
Apache Licence也是对商业应用友好的许可。使用者也可以在需要的时候修改代码来满足需要并作为开源或商业产品发布/销售。
GPL(GNU General Public License)
我们很熟悉的Linux就是采用了GPL。GPL协议和BSD, Apache
Licence等鼓励代码重用的许可很不一样。GPL的出发点是代码的开源/免费使用和引用/修改/衍生代码的开源/免费使用,但不允许修改后和衍生的代
码做为闭源的商业软件发布和销售。这也就是为什么我们能用免费的各种linux,包括商业公司的linux和linux上各种各样的由个人,组织,以及商
业软件公司开发的免费软件了。
GPL协议的主要内容是只要在一个软件中使用("使用"指类库引用,修改后的代码或者衍生代
码)GPL
协议的产品,则该软件产品必须也采用GPL协议,既必须也是开源和免费。这就是所谓的"传染性"。GPL协议的产品作为一个单独的产品使用没有任何问题,
还可以享受免费的优势。
由于GPL严格要求使用了GPL类库的软件产品必须使用GPL协议,对于使用GPL协议的开源代码,商业软件或者对代码有保密要求的部门就不适合集成/采用作为类库和二次开发的基础。
其它细节如再发布的时候需要伴随GPL协议等和BSD/Apache等类似。
LGPL(GNU Lesser General Public License)
LGPL是GPL的一个为主要为类库使用设计的开源协议。和GPL要求任何使用/修改/衍生
之GPL类库的的软件必须采用GPL协议不同。LGPL
允许商业软件通过类库引用(link)方式使用LGPL类库而不需要开源商业软件的代码。这使得采用LGPL协议的开源代码可以被商业软件作为类库引用并
发布和销售。
但是如果修改LGPL协议的代码或者衍生,则所有修改的代码,涉及修改部分的额外代码和衍生
的代码都必须采用LGPL协议。因此LGPL协议的开源
代码很适合作为第三方类库被商业软件引用,但不适合希望以LGPL协议代码为基础,通过修改和衍生的方式做二次开发的商业软件采用。
GPL/LGPL都保障原作者的知识产权,避免有人利用开源代码复制并开发类似的产品
MIT(MIT)
MIT是和BSD一样宽范的许可协议,作者只想保留版权,而无任何其他了限制.也就是说,你必须在你的发行版里包含原许可协议的声明,无论你是以二进制发布的还是以源代码发布的.
Public Domain
公共域授权。将软件授权为公共域,这些软件包没有授权协议,任何人都可以随意使用它。
Artistic许可使作者保持对进一步开发的控制。
以下为对GPL协议的进一步解释:
=================================================================================
GNU通用公共许可证(GNU General Public License)(英文通常以GNU GPL或是直接簡短的以GPL表示),是一個廣泛被使用的自由軟體許可證,最初由理查德·斯托曼为GNU计划而撰写。此许可证最新版本为“版本3”,2007年6月29日发布。GNU宽通用公共许可证(GNU Lesser General Public License,一般簡稱LGPL)是改自GPL的另一個版本,其目的是為了應用於一些軟體函式庫。
GPL給予了電腦程式自由軟體的定義,並且使用了所謂的"Copyleft"來確保程式的自由被完善的保留。
自由
GPL授予程序接受人以下权利,或称“自由”:
- 以任何目的运行此程序的自由;
- 以学习程序工作机理为目的,对程序进行修改的自由(能得到源代码是前提);
- 再发行复制件的自由;
- 改进此程序,并公开发布改进的自由(能得到源代码是前提)
相反地,随版权所有软件的最终用户许可证几乎从不授予用户任何权利(除了使用的权利),甚至可能限制法律允许的行为,比如逆向工程。
GPL与其他一些更“许可的”自由软件许可证(比如BSD许可证)相比,主要区别就在于GPL寻求确保上述自由能在复制件及演绎作品中得到保障。它通过一种由斯托曼发明的叫Copyleft的法律机制实现,即要求GPL程序的演绎作品也要在GPL之下。相反,BSD式的许可证并不禁止演绎作品变成专有软件。
由于某些原因,GPL成为了自由软件和开源软件的最流行许可证。到2004年4月,GPL已占Freshmeat上所列的自由软件的约75%,SourceForge的约68%。类似的,2001年一项关于Red Hat Linux 7.1的调查显示一般的代码都以GPL发布。著名的GPL自由软件包括Linux核心和GCC。
历史
GPL由斯托曼撰写,用于GNU计划。它以GNU Emacs、GDB、GCC的许可证的早期版本为蓝本。这些许可证都包含有一些GPL的版权思想,但仅只针对特定程序。斯托曼的目标就是创造出一种四海之内皆可使用的许可证,这样就能为许多源代码共享计划带来福音。GPL版本1就这样,在1989年1月诞生。
到1990年时,因为一些共享库而出现了对比GPL更宽松的许可证的需求。所以当GPL版本2在1991年6月发布时,另一许可证——库通用许可证(Library General Public License,简称LGPL)也随之发布,并记作“版本2”以示对GPL的补充。版本号在LGPL版本2.1发布时不再相同,而LGPL也被重命名为GNU宽通用公共许可证(Lesser General Public License)以体现GNU哲学观。
GPLv1
GPL版本1,即最初的版本,发布于1989年一月,其目的是防止那些阻碍自由软件的行为,
而这些阻碍软件开源的行为主要有两种(一种是软件发布者只发布可执行的二进制代码而不发布具有源代码,一种是软件发布者在软件许可加入限制性条款)。因此
按照GPLv1,如果发布了可执行的二进制代码,就必须同时发布可读的源代码,并且在发布任何基于GPL许可的软件时,不能添加任何限制性的条款。
GPL2
理查德·斯托曼在GPLv2中所做的最大的改动就是增加了“自由还是死亡”("Liberty or Death")这章条款,即第七章liberty-or-death Presentation。这章中申明道,如果哪个人在发布源于GPL的软件的时候,同时添加强制的条款,以在一定程度上保障和尊重其它一些人的自由和权益(也就是说在一些国家里,人们只能以二进制代码的形式发布软件,以保护开发软件者的版权),那么他将根本无权发布该软件。
到了1990年,人们普遍认为一个限制性弱的许可证对于自由软件的发展是有战略意义上的好处的;因此,当GPL的第二个版本(GPLv2)在1991年6月发布时,与此同时第二个许可证程式庫GNU通用公共许可证(LGPL,the Library General Public License )也被发布出来并且一开始就将其版本定为第2版本以表示其和GPLv2的互补性。这个版本一直延续到1999年,并分支出一个衍生的LGPL版本号为2.1,并将其重新命名为轻量级通用公共许可证(又称宽通用公共许可证)(Lesser General Public License)以反应其在整个GNU哲学中的位置。
到2005年,GPL版本3正由斯托曼起草,由伊本·莫格林和軟件自由法律中心(Software Freedom Law Center)[1]提供法律咨询。
斯托曼在2006年2月25日自由及开源软件开发者欧洲会议的演讲上说:([2])
- 在所有的改动中,最重要的四个是:
- 解决软件专利问题;
- 与其他许可证的兼容性;
- 源代码分割和组成的定义;
- 解决数字版权管理 (DRM) 问题。
2006年,自由软件基金会针对GPL的可能的修改开始了12个月的公共咨询。
GPLv3草稿[2]于2006年1月16日开始可用。版本2与3的非官方比较对照参见:[3],[4]。
2007年3月28日正式启用。
2007年6月29日,自由软件基金会正式发布了GPL第3版[3]。
条款
以下是对GPL条款的一个通俗易懂的总结。而GPL原文文本才是真正法律上精确的。该文本的链接可从本页底部获得。
授予的权利
此GPL的条款和条件适用于任何收到GPL下的作品的人(即“许可证接受人”)。任何接受这
些条款和条件的许可证接受人都有修改、复制、再发行作品或作品的演绎版本的授权。许可证接受人可以对此项服务收取费用
,反之亦然。这一点是GPL与其他禁止商业用途的自由软件许可证最大的不同。Stallman认为自由软件不应限制其商业用途,同时GPL清楚地说明了这
一点。
但GPL又规定发行者不能限制GPL授予的权利。例如,这禁止对软件在单纯沉默(消极默示)式协议或合同下的发行。GPL下的发行者同时也同意在软件中使用的专利可以在其它GPL软件中使用。
Copyleft
GPL不会授予许可证接受人无限的权利。再发行权的授予需要许可证接受人开放软件的源代码,及所有修改。且复制件、修改版本,都必须以GPL为许可证。
这些要求就是copyleft,它的基础就是作品在法律上版权所有。由于它版权所有,许可证接受人就无权进行修改和再发行(除合理使用),除非它有一个copyleft条款。如果某人想行使通常被法律所禁止的权利,只需同意GPL的条款。相反地,如果某人发行软件违反了GPL(比如不开放源代码),他就有可能被原作者起诉。
copyleft利用版权法来达到与其相反的目的: copyleft给人不可剥夺的权利,而不是版权法所规定的诸多限制。这也是GPL被称作“被黑的版权法”的原因。
许多GPL软件发行者都把源代码与可执行程序捆绑起来。另一方式就是以物理介质(比如CD)为载体提供源代码。在实践中,许多GPL软件都是在互联网上发行的,源代码也有许多可以FTP方式得到。
copyleft只在程序再发行时发生效力。对软件的修改可以不公开或开放源代码,只要不发行。注意copyleft只对软件有效力,而对软件的输出并无效力(除非输出的是软件本身)。不过这在GPL版本3中可能会有改动。
GPL是许可证
GPL设计为一种许可证,而不是合同。在英美法系国家,许可证与合同有法律上的明确区别:合同由合同法保障效力,而GPL作为一种许可证由版权法保障效力。不过在许多采用欧陆法系的国家并无此种区别。
GPL原理简单:在版权法下,你不遵守GPL的条款和条件你就没有相应权利。而作品在没有GPL的情况下,版权法作为默认条款发生效力,而不是作品进入公有领域。
版权所有人
GPL文本是版权所有的,且著作权人是自由软件基金会。但是,自由软件基金会没有在GPL下发行作品的著作权(除非作者制定自由软件基金会是著作权人)。只有著作权人才有权对许可证的违反进行起诉。
自由软件基金会允许人们使用以GPL为基础的其他许可证,但不允许演绎的许可证未经授权地使用GPL的前言。不过像这样的许可证通常与GPL不兼容。[4]
GNU计划创立的其他许可证包括:GNU宽通用公共许可证和GNU自由文档许可证。
争议
一个关于GPL重要的争议是,非GPL软件是否可以动态链接到GPL库。GPL对GPL作品的演绎作品在
GPL下发布规定很明确。但是对于动态链接到GPL库的作品是否是演绎作品就规定得不清楚了。自由和开放源代码社区为此分成两派,自由软件基金会认为这种
作品就是演绎作品,但其他专家并不同意。这个问题根本的并不关乎GPL本身,而是一个版权法如何定义演绎作品。美国联邦上诉法院第九巡回审判庭在
Galoob v. Nintendo案对演绎作品尝试定义,但最终没有明确的结果。
不幸的是,许多开发者觉得这是个技术问题。但实际上这完全是法律问题。不过由于迄今为止没有案例表明有人以动态链接的方式来绕过GPL的条款或者并被起诉,动态链接的限制已经是事实上地(de facto)有效,不论它是否是法律上地(de jure)有效。
2002年,MySQL AB公司起诉Progress NuSphere侵犯版权和商标。NuSphere被指以链接代码的形式侵犯了著作权。最终此案以调解结束。在听证期间,法官“认为没有什么原因”(不管是否是动态链接)会使得GPL失去法律效力。
2003年8月,SCO Group称他们认为GPL没有法律效力,且准备就在Linux核心中使用的SCO Unix代码进行诉讼。参见SCO诉IBM。
2004年4月,在SiteCom拒绝停止发行Netfilter项目的GPL软件后,慕尼黑地区法庭据对GPL条款的侵犯判定对SiteCom进行临时性禁令(诉前停止侵犯专利权行为的措施)。同年7月,法庭确认此勒令为对SiteCom最终判决。此判决明显的印证了自由软件基金会的法律顾问伊本·莫格林的预言:
- “被告侵犯了原告的著作权:提供了软件netfilter/iptables的广告及下载,但没有遵守GPL的条款。可以说,如果被告有许可证许
可,这些行为是完全合法的……原被告就GPL是否达成协议这是一个独立的问题。如果当事人没有同意,被告将没有复制、发行、公开
‘netfilter/iptables’的权利。”
此判决十分重要,因为它是全球首次法庭确认GPL是有法律效力的。
2005年5月,Daniel Wallace于美国联邦印第安纳南区地方法院起诉自由软件基金会,因为二者对GPL是否是非法意见不一。后诉讼于3月结束,因为Wallace没有有效的反托拉斯陈述。法庭注意到“GPL鼓励,而不是反对电脑操作系统的自由竞争和发行,这直接使消费者受益。”[5]Wallace被拒绝改变诉由,并被要求支付诉讼费用。
兼容性
大多数自由软件许可证,比如MIT/X许可证、BSD许可证、LGPL,都是“GPL兼容的”,即它们的代码与GPL代码混用无冲突(但新代码则是GPL下的)。但是有某些开源软件许可证不是GPL兼容的。通常意见是开发者仅只使用GPL兼容的许可证,以免法律问题。
参见软件许可证列表以查证兼容性。
批评
2001年微软的首席执行官史蒂夫·巴爾默称
Linux为“癌症”,因为GPL的影响。微软批评者指出,微软憎恶GPL的真正原因是因为对微软的“包围、扩展、消灭”策略起了反作用。注意微软已以
GPL为许可证发行了SFU(Microsoft Windows Services for UNIX)中所包含的部分组件,例如GCC。
GPL的批评者常常认为GPL是有“传染性”的“病毒”
,因为GPL条款规定演绎作品也必须是GPL的。由于“演绎作品”通常被解释为包含GPL代码或动态链接到GPL库(如上)的软件,“病毒说”来源于
GPL对于许可证的强制继承的要求。这正是GPL与BSD式许可证的哲学思想上的差异。GPL的支持者确信自由软件世界应具有自我保护能力和可持续发展性
——确保自由软件的演绎作品同样“自由”,但其他人认为自由软件应给予“所有人”最大的自由。
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!822.entry
今天在项目里碰到了非常纠结的问题——中文乱码问题。
原因是这样的,我打算通过JSF的一个FRAME控件LOAD一个jsp页面,该jsp页面根据GET参数得到文件的路径和文件名,从而可以将文件用二进制流输出给浏览器,以便下载。
但是由于我本地的文件名是中文的,所以出现了乱码问题。
简单点说,主要有以下几点:
1. 用java创建本地文件的中文文件名问题:
为了保证你的文件名不会乱码,在用java.io.File对象创建文件的时候,构造函数里的filename千万不要随意的转换成其他字符集的。就用默认的就好。也就是说
File f= new File(“中文文件.txt”);
足矣。这样,无论Windows用GBK编码文件名还是LINUX用UTF-8编码文件名,都可以在当前的系统中正常的查看。
2.向JSP传递中文参数的时候,如果你没有设定Tomcat的全局URIEncoding,一定要把中文参数进行URLEncoding
URLEncoder.encode(requestUrl,"UTF-8");
在被请求的页面,执行URLDecoder.decode(request.getParameter(“param”),"UTF-8");从而得到正确的中文。如果不能,可以尝试“new String(request.getParameter(“param”).getBytes("iso8859_1"),”UTF-8”)”
3.如果你在POST传参的时候发现出了问题,
可以在web.xml里面配置一个CharacterEncodingFillter。这个东西网上有很多代码,自己找吧。如果你用的是Spring,可以用它自带的org.springframework.web.filter.CharacterEncodingFilter
4.另一种解决参数乱码的办法就是修改TOMCAT的配置文件。
找到server.xml。把里面HTTP端口和HTTPS端口(如果你放开了)的Connector元素后面加上URIEncoding=”UTF-8”字样。
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!819.entry
做的项目中要用到日志功能,记录重要数据增删改,以提供后台动态数据恢复功能,在数据库中建立一个表四个字段:
id:标识(long)、action:增删改类别(String 或 int)、olddata与newdata分别记录增删改前后的数据类型为blob、optime记录操作时间
项目持久层用了Hibernate所以数据库中所有条目都是以JavaBean形式出现,JavaBean扩展了Serializable可以实现对象的序列化,现在问题就是怎样保存JavaBean序列化的结果到数据库,并且可以逆向反序列化为实例。
因为刚接触Java对列的概念不是很清楚,所以在序列化上遇到了问题,首先是如何不通过临时文件取得对象序列化的结果,网上的例子大多是对文件流的操作,用来保存图片
综合网上的多个例子以及从JDK中查询的结果,总结出以下过程:
1、还是对流的操作,不过不是文件流而是字节流,利用ByteArrayOutputStream创建
2、通过new出来ByteArrayOutputStream作为参数创建ObjectOutputStream
3、调用ObjectOutputStream的writeObject将任意JavaBean序列化为字节流
//以上是序列化过程,实际上使用不同的XStream就可以把JavaBean序列化到不同的流中
4、通过调用ByteArrayOutputStream的toByteArray可以获得byte数组
//这是取中间值,相当于文件流操作时利用文件名打开一个文件流,文件名也是一个中间值
5、将得到的byte数组作为参数用ByteArrayInputStream打开一个输入流
6、调用静态方法Hibernate.createBlob(),以输入流为参数获取Blob
7、此后可将该Blob设置为接收Bean的属性保存到数据库中
//以上完成将序列化的结果存储到数据库
8、利用Hibernate的API的到数据库中的Blob很容易,然后调用Blob的getBinaryStream可获取输入流,将此流作为ObjectInputStream,调用readObject可得到序列化前的实例
代码:
//序列化
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(new User("cfgxy"));
//保存到数据库,sessionFactory是Hibernate中SessionFactory的一个实例
Session session=sessionFactory.createSession();
Transaction tx =session.openTransaction();
ByteArrayInputStream bis=new ByteArrayInputStream(bos.getByteArray());
session.save(new Logs(null,"INSERT",null,Hibernate.createBlob(bis)));
tx.commit();
session.close();
//从数据库读取,假设传来的参数id为数据库主键的值
Session session=sessionFactory.createSession();
Logs log=(Logs)session.load(Logs.class,id);
ObjectInputStream ois=new ObjectInputStream(log.getNewData().getBinaryStream());
return (User)ois.readObject();
//代码中均未对异常进行捕捉,实际运用中要捕捉异常
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!818.entry
"java深度历险"一书在讲解“类装载”的一章中,举了以下的例子: 引用 Java代码 - public interface Assembly{
- public void start();;
- }
-
- public class Word implements Assembly{
- static{
- System.out.println("Word static initialization!");;
- }
-
- public void start();{
- System.out.prinlnt("Word starts");;
- }
- }
-
- public class Office{
- public static void main(String args[]); throws Exception{
- Office off = new Office();;
- System.out.println("类别准备载入");;
- Class c = Class.forName(args[0],true,off.getClass();.getClassLoader(););;
- System.out.println("类别准备实例化");;
- Object o = c.newInstance();;
- Object o2= c.newInstance();;
- }
- }
执行java Office Word,运行结果如下: “Loaded Office” “类别准备载入” “Loaded Accembly” “Loaded Word”” “Word static initialization” “类别准备实体化”。 但是如果将Office.java中Class.forName(args[0],true,off.getClass().getClassLoader())中的true变为false,再执行java Office Word结果显示为: “Loaded Office” “类别准备载入” “Loaded Accembly” “Loaded Word”” “类别准备实体化” “Word static initialization”。 显然两次红字部分顺序相反,及static块执行的顺序不同。此书作者提出了原因,原文: 引用 “过去很多java书上提到静态初始化(static initializion block)时,都会说静态初始化区块只是在类第一次载入的时候才会被调用仅仅一次。可是上面输出却发现即使类被载入了,其静态初始化区块也没有被调用,而是在第一次调用newInstance方法时,静态初始化块才被真正调用,应该改成-静态初始化块只是在类被第一次实体化的时候才会被仅仅调用一次。” 其实,该书作者的上述描述有误。通过一个试验,就可以看出谬误所在。 Java代码 - public class TestA{
- static{
- System.out.println("Static block executed!");;
- }
- }
-
- public class Test{
- public static void main(String args[]);{
- Test test = new Test();;
- Class.forName("TestA",true,test.getClass();.getClassLoader(););;
- }
- }
运行一下,相信大家一定可以看到,“Static block executed!”的输出。这与 引用 而是在第一次调用newInstance方法时,静态初始化块才被真正调用 的说法矛盾。 其实我想事实是这样的: 一个类的运行,JVM做会以下几件事情 1、类装载 2、链接 3、初始化 4、实例化;而初始化阶段做的事情是初始化静态变量和执行静态方法等的工作。所以,当Class.forName(args[0],true,off.getClass().getClassLoader());中的true变为false的时候,就是告诉JVM不需再load class之后进行initial的工作。这样,将initial的工作推迟到了newInstance的时候进行。所以,static块的绝对不是什么“只是在类被第一次实体化的时候才会被仅仅调用一次”,而应该是在类被初始化的时候,仅仅调用一次。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!817.entry
一、性能优化的一般概念 人们普遍认为Java程序总是比C程序慢,对于这种意见,大多数人或许已经听得太多了。实际上,情况远比那些陈旧的主张要复杂。许多 Java程序确实很慢,但速度慢不是所有Java程序的固有特征。许多Java程序可以达到C或C++中类似程序的效率,但只有当设计者和程序员在整个开发过程中密切注意性能问题时,这才有可能。 本文的主要目的是讨论如何优化Java IO操作的性能。许多应用把大量的运行时间花在网络或文件IO操作上,设计拙劣的IO代码可能要比经过精心调整的IO代码慢上几倍。 说到Java程序的性能优化,有些概念总是一次又一次地被提起。本文的示例围绕IO应用的优化展开,但基本原则同样适用于其他性能情况。 对于性能优化来说,最重要的原则也许就是:尽早测试,经常测试。不知道性能问题的根源就无法有效地调整性能,许多程序员因为毫无根据地猜测性能问题的所在而徒劳无功。在一个只占程序总运行时间百分之一的模块上花费数天时间,应用性能的改进程度不可能超过百分之一。所以,应当避免猜测,而是采用性能测试工具,比如一些代码分析工具或带有时间信息的日志,找出应用中耗时最多的地方,然后集中精力优化这些程序的热点。性能调整完成后,应当再次进行测试。测试不仅有助于程序员把精力集中在那些最重要的代码上,而且还能够显示出性能调整是否真地取得了成功。 在调整程序性能的过程中,需要测试的数据可能有很多,例如运行总时间、内存占用平均值、内存占用峰值、程序的吞吐能力、请求延迟时间以及对象创建情况等。到底应该关注哪些因素,这与具体的情况和对性能的要求有关。大部分上述数据都可以通过一些优秀的商品化分析工具测试得到,然而,并非一定要有昂贵的代码分析工具才能收集得到有用的性能数据。 本文收集的性能数据只针对运行时间,测试所用的工具类似于下面的Timer类(可以方便地对它进行扩展,使它支持pause()和 restart()之类的操作)。带有时间信息的日志输出语句会影响测试结果,因为这些语句也要创建对象和执行IO操作,Timer允许我们在不用这类语句的情况下收集时间信息。
public class Timer { // 一个简单的“秒表”类,精度为毫秒。 private long startTime, endTime; public void start() { startTime = System.currentTimeMillis(); } public void stop() { endTime = System.currentTimeMillis(); } public long getTime() { return endTime - startTime; } } |
引起Java性能问题的常见原因之一是过多地创建临时对象。虽然新的Java虚拟机在创建许多小型对象时有效地降低了性能影响,但对象创建属于昂贵操作这一事实仍旧没有改变。由于字符串对象不可变的特点,String类常常是性能问题最大的罪魁祸首,因为每次修改一个String对象,就要创建一个或者多个新的对象。由此可以看出,提高性能的第二个原则是:避免过多的对象创建操作。
二、IO性能优化
许多应用要进行大规模的数据处理,而IO操作正属于那种细微的改动会导致巨大性能差异的地方。本文的例子来自对一个文字处理应用的性能优化,这个文字处理应用要对大量的文本进行分析和处理。在文字处理应用中,读取和处理输入文本的时间很关键,优化该应用所采用的措施为上面指出的性能优化原则提供了很好的例子。
影响Java IO性能最主要的原因之一在于大量地使用单字符IO操作,即用InputStream.read()和Reader.read()方法每次读取一个字符。 Java的单字符IO操作继承自C语言。在C语言中,单字符IO操作是一种常见的操作,比如重复地调用getc()读取一个文件。C语言单字符IO操作的效率很高,因为getc()和putc()函数以宏的形式实现,且支持带缓冲的文件访问,因此这两个函数只需要几个时钟周期就可以执行完毕。在Java 中,情况完全不同:对于每一个字符,不仅要有一次或者多次方法调用,而且更重要的是,如果不使用任何类型的缓冲,要获得一个字符就要有一次系统调用。虽然一个依赖read()的Java程序可能在表现、功能上和C程序一样,但两者在性能上不能相提并论。幸而,Java提供了几种简单的办法帮助我们获得更好的IO性能。
缓冲可以用以下两种方式之一实现:使用标准的BufferedReader和BufferedInputStream类,或者使用块读取方法一次读取一大块数据。前者快速简单,能够有效地改进性能,且只需少量地增加代码,出错的机会也较少。后者也即自己编写代码,复杂性略有提高——当然也说不上困难,但它能够获得更好的效果。
为测试不同IO操作方式的效率,本文用到了六个小程序,这六个小程序读取几百个文件并分析每一个字符。表一显示了这六个程序的运行时间,测试用到了五个常见的Linux Java虚拟机:Sun 1.1.7、1.2.2和1.3 Java虚拟机,IBM 1.1.8和1.3 Java虚拟机。
这六个程序是:
- RawBytes:用FileInputStream.read()每次读取一个字节。
- RawChars:用FileReader.read()每次读取一个字符。
- BufferedIS:用BufferedInputStream封装FileInputStream,用read()每次读取一个字节的数据。
- BufferedR:用BufferedReader封装FileReader,用read()每次读取一个字符。
- SelfBufferedIS:用FileInputStream.read(byte[])每次读取1 K数据,从缓冲区访问数据。
- SelfBufferedR:用FileReader.read(char[])每次读取1 K数据,从缓冲区访问数据。
表一 |
|
Sun 1.1.7 |
IBM 1.1.8 |
Sun 1.2.2 |
Sun 1.3 |
IBM 1.3 |
RawBytes |
20.6 |
18.0 |
26.1 |
20.70 |
62.70 |
RawChars |
100.0 |
235.0 |
174.0 |
438.00 |
148.00 |
BufferedIS |
9.2 |
1.8 |
8.6 |
2.28 |
2.65 |
BufferedR |
16.7 |
2.4 |
10.0 |
2.84 |
3.10 |
SelfBufferedIS |
2.1 |
0.4 |
2.0 |
0.61 |
0.53 |
SelfBufferedR |
8.2 |
0.9 |
2.7 |
1.12 |
1.17 |
表一是调整Java VM和程序启动配置之后,处理几百个文件的总计时间。从表一我们可以得到几个显而易见的结论:
- InputStream比Reader高效。一个char用两个字节保存字符,而byte只需要一个,因此用byte保存字符消耗的内存和需要执行的机器指令更少。更重要的是,用byte避免了进行Unicode转换。因此,如果可能的话,应尽量使用byte替代char。例如,如果应用必须支持国际化,则必须使用char;如果从一个ASCII数据源读取(比如HTTP或MIME头),或者能够确定输入文字总是英文,则程序可以使用byte。
- 无缓冲的字符IO实在很慢。字符IO本来就效率不高,如果没有缓冲,情形就更糟了。因此,在编程实践中,至少应该为流加上缓冲,它可以让IO性能提高10倍以上。
- 带有缓冲的块操作IO要比缓冲的流字符IO快。对于字符IO,虽然缓冲流避免了每次读取字符时的系统调用开销,但仍需要一次或多次方法调用。带缓冲的块IO比缓冲流IO快2到4倍,比无缓冲的IO快4到40倍。
从表一不易看出的一点是,字符IO可能抵消速度较快的Java VM带来的优势。在大多数性能测试中,IBM 1.1.8 Linux Java VM大约有Sun 1.1.7 Linux Java VM两倍那么快,然而在RawBytes和RawChars的测试中,结果显示出两者差不多慢,它们花在系统调用上的额外时间开销掩盖了较快Java VM带来的速度优势。
块IO还有另一个不那么明显的优点。缓冲的字符IO有时对组件之间的协调有更多的要求,带来更多的出错机会。很多时候,应用中的IO操作由一个组件完成,应用把一个Reader或InputStream传递给组件,然后,IO组件处理流的内容。一些IO组件可能错误地假设它所操作的流是一个带缓冲的流,但又不在文档中说明这方面的需求,或者虽然IO组件在文档中说明了这方面的要求,但应用的开发者却未能留意到这一点。在这些情况下,IO 操作将不按意料之中地那样带有缓冲,从而带来严重的性能问题。如果改用块IO,这类情形就不可能出现(因此,设计软件组件时,最好能够做到组件不可能被误用,而不要依赖于文档来保证组件的正确使用)。
从上述简单的测试可以看出,用最直接的方法完成一个简单任务,比如读取文本,可能比细心选择的方法慢40到60倍。在这些测试中,程序在提取和分析每一个字符时进行了一些计算。如果程序只是把数据从一个流复制到另一个流,则非缓冲的字符IO和块IO之间的性能差异将更加明显,块IO的性能将达到非缓冲字符IO的300到500倍。
三、再次测试
性能调整必须反复地进行,因为在主要性能问题解决之前,次要性能问题往往不能显露出来。在文字处理应用的例子中,最初的分析显示出程序把绝大部分的时间花费在读取字符上,加上缓冲功能后性能有了戏剧性的提高。只有在程序解决了主要的性能瓶颈(字符IO)之后,剩余的性能热点才显现出来。对程序的第二次分析显示出,程序在创建String对象上花费了大量的时间,而且看起来它为输入文本中的每一个单词创建了一个以上的String对象。
本文例子中的文本分析应用采用了模块化的设计,用户可以结合多个文本处理操作达到预期的目标。例如,用户可以结合运用单词标识器部件(读取输入字符并把它们组织成单词)和小写字母转换器部件(把单词转换成小写字母),以及一个还原器部件(把单词转换成它们的基本形式,例如,把 jumper和jumped转换成jump)。
虽然模块化构造具有很明显的优点,但这种处理方式会对性能产生负面影响。由于部件之间的接口是固定的(每一个部件都以一个String 作为输入,并输出另一个String),部件之间也许存在一些重复的操作。如果有几个部件经常组合在一起使用,对这些情形进行优化是值得的。
在这个文字处理系统中,从实际使用情况可以看出,用户几乎总是在使用单词标识器部件之后,紧接着使用小写字母转换器部件。单词标识器分析每一个字符,寻找单词边界,同时填充一个单词缓冲区。标识出一个完整的单词之后,单词标识器部件将为它创建一个String对象。调用链中的下一个部件是小写字母转换器部件,这个部件将在前面得到的String上调用String.toLowerCase(),从而创建了另一个String对象。对于输入文本中的每一个单词,顺序使用这两个部件将生成两个String对象。由于单词标识器部件和小写字母转换器部件频繁地一起使用,因此可以添加一个经过优化的小写字母单词标识器,这个标识器具有原来两个部件的功能,但只为每一个单词创建一个String对象,从而有利于提高性能。表二显示了测试结果:
表二 |
|
|
Sun 1.1.7 |
IBM 1.1.8 |
Sun 1.2.2 |
Sun 1.3 |
IBM 1.3 |
A |
单词标识 |
23.0 |
3.6 |
10.7 |
2.6 |
2.9 |
B |
单词标识 + 小写字母转换 |
39.6 |
6.7 |
13.9 |
3.9 |
3.9 |
C |
结合单词标识和小写字母转换 |
29.0 |
3.8 |
12.9 |
3.1 |
3.1 |
|
临时字符串创建时间 (B-C) |
10.6 |
2.9 |
1.0 |
0.8 |
0.8 |
从表二我们可以得到几个有用的发现:
- 对于Java VM 1.1,简单的优化引人注目地提高了性能:大约在百分之二十五到百分之四十五之间。最后一行显示出,创建临时String对象占用了程序A和程序B之间百分之六十到九十的性能增加值。另外,正如其他几个测试项目显示出的,IBM Java VM 1.1运行速度要比Sun Java VM 1.1快。
- 对于1.2和1.3的Java VM,两个版本之间的性能差异不再那么大,大约只有百分之十到百分之二十五之间,相当于创建临时String对象所耗时间的百分比。这个结果表明,在创建对象实例方面,版本较高的Java VM确实提高了效率,但过多的对象创建操作对性能的影响仍旧值得注意。
- 对于这类创建大量小型对象的操作,1.3版本的Java VM要比1.1和1.2版本的Java VM快得多。
性能优化是一种需要反复进行的工作。在开发工作的早期阶段开始收集性能数据是值得的,因为这样可以尽早地找出和调整性能热点。通过一些比较简单的改进,比如为IO操作增加缓冲,或在适当的时候用byte替代char,常常可以戏剧性地提高应用的性能。另外,不同的VM之间也有着很大的性能差异,简单地换上一个速度较快的Java VM,可能就让程序的性能向预期的目标跨出了一大步。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!813.entry
Java类如下 public static void downloadFile(String path,String fileName) { try { // 获得JSF上下文环境 FacesContext context = FacesContext.getCurrentInstance(); // 获得ServletContext对象 ServletContext servletContext = (ServletContext) context .getExternalContext().getContext(); // 取得文件的绝对路径 String realName = servletContext.getRealPath(path) + "/" + fileName; HttpServletResponse httpServletResponse = (HttpServletResponse) FacesContext .getCurrentInstance().getExternalContext().getResponse(); downloadFile(httpServletResponse,realName,fileName); } catch (IOException e) { e.printStackTrace(); } FacesContext.getCurrentInstance().responseComplete(); } public static void downloadFile(HttpServletResponse response,String realName,String fileName) throws IOException { response.setHeader("Content-disposition", "attachment; filename=" + fileName); response.setContentType("application/x-download"); //File exportFile = new File(realName); //response.setContentLength((int) exportFile.length()); ServletOutputStream servletOutputStream = response.getOutputStream(); byte[] b = new byte[1024]; int i = 0; FileInputStream fis = new java.io.FileInputStream(realName); while ((i = fis.read(b)) > 0) { servletOutputStream.write(b, 0, i); } } 使用方法 1、在backing bean的方法中调用函数1即可。如Abean中download方法调用了该方法,前台可以这样调用: <h:commandButton value="download" action="#{aBean.download}"></h:commandButton> 或者 <h:commandLink value="download" action="#{fileUploadForm.download}"></h:commandLink> 2、jsp页面可以这样调用: <%@ page contentType="text/html; charset=gb2312"%><%@page import="java.io.*"%><% String filename = ""; if (request.getParameter("filename") != null) { filename = request.getParameter("filename"); } try { framework.util.FileUtils.downloadFile(response,getServletContext().getRealPath(filename),filename); } catch(final IOException e) { System.out.println ( "出现IOException." + e ); } catch(final IllegalStateException e) { System.out.println ( "出现IllegalStateException." + e ); } %> 于是jsf页面我们可以借助outputlink来调用该页面 <h:outputLink id="downloadfile" value="#{page/FileDownload.jsp?filename=}"> <t:outputText value="下载文件" /> </h:outputLink> 文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!812.entry
最近在单位的电脑上调试程序。由于使用了JPA和OperaMasks这两个在容器启动的时候需要扫描实体Bean和LiteBean的框架,所以,在Tomcat启动初期,系统经常由于大量的对象被创建而不能回收导致PermGen Space Over Flow。在网上经过一番搜索,终于找到了如下的启动参数,经测试多次热部署不会导致VM崩溃。 -Xms128m -Xmx512m -Xmn96m -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=7 -XX:GCTimeRatio=19 -Xnoclassgc -XX:+DisableExplicitGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSPermGenSweepingEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:-CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=70 -XX:SoftRefLRUPolicyMSPerMB=0 文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!799.entry
【转自】http://hi.baidu.com/kangqii/blog/item/f8495043f70c3a1572f05d4e.html 最近要做一个CRM,登录时数字证书要有验证的功能,在用户登录时除了效验用户名密码,还需验证其数字证书 相关资源:IBM developerWroks中国中的tomcat4中使用SSL,javaeye中的Acegi X.509双向认证 与tomcat4中使用SSL中的异同:jdk1.4中已经包含JSSE 与AcegiX.509双向认证中的异同:tomcat6配置文件多了SSLEnabled="true"属性 1.生成CA证书目前不使用第三方权威机构的CA来认证,自己充当CA的角色 1.创建私钥 :C:\OpenSSL\apps>openssl genrsa -out root/root-key.pem 1024 2.创建证书请求 :C:\OpenSSL\apps>openssl req -new -out root/root-req.csr -key root/root-key.pem 3.自签署证书 :C:\OpenSSL\apps>openssl x509 -req -in root/root-req.csr -out root/root-cert.pem -signkey root/root-key.pem -days 3650 4.将证书导出成浏览器支持的.p12格式 :C:\OpenSSL\apps>openssl pkcs12 -export -clcerts -in root/root-cert.pem -inkey root/root-key.pem -out root/root.p12 2.生成server证书 1.创建私钥 :C:\OpenSSL\apps>openssl genrsa -out server/server-key.pem 1024 2.创建证书请求 :C:\OpenSSL\apps>openssl req -new -out server/server-req.csr -key server/server-key.pem 3.自签署证书 :C:\OpenSSL\apps>openssl x509 -req -in server/server-req.csr -out server/server-cert.pem -signkey server/server-key.pem -CA root/root-cert.pem -CAkey root/root-key.pem -CAcreateserial -days 3650 4.将证书导出成浏览器支持的.p12格式 :C:\OpenSSL\apps>openssl pkcs12 -export -clcerts -in server/server-cert.pem -inkey server/server-key.pem -out server/server.p12 3.生成client证书 1.创建私钥 :C:\OpenSSL\apps>openssl genrsa -out client/client-key.pem 1024 2.创建证书请求 :C:\OpenSSL\apps>openssl req -new -out client/client-req.csr -key client/client-key.pem 3.自签署证书 :C:\OpenSSL\apps>openssl x509 -req -in client/client-req.csr -out client/client-cert.pem -signkey client/client-key.pem -CA root/root-cert.pem -CAkey root/root-key.pem -CAcreateserial -days 3650 4.将证书导出成浏览器支持的.p12格式 :C:\OpenSSL\apps>openssl pkcs12 -export -clcerts -in client/client-cert.pem -inkey client/client-key.pem -out client/client.p12 4.根据root证书生成jks文件 C:\OpenSSL\apps\root>keytool -import -v -trustcacerts -storepass password -alias root -file root-cert.pem -keystore root.jks 5.配置tomcat ssl,修改conf/server.xmltomcat6中多了SSLEnabled="true"属性 keystorefile, truststorefile设置为你正确的相关路径 xml 代码 - <connector secure="true" scheme="https" protocol="HTTP/1.1" port="8443"
- sslenabled="true" maxhttpheadersize="8192" maxthreads="150"
- minsparethreads="25" maxsparethreads="75" enablelookups="false"
- disableuploadtimeout="true" acceptcount="100" sslprotocol="TLS"
- clientauth="true" keystorefile="d:/path/bin/x509/server.p12"
- keystoretype="PKCS12" keystorepass="123456" truststorefile="d:/path/bin/x509/root.jks"
- truststoretype="JKS" truststorepass="123456"/>
6.将root.p12,client.p12分别导入到IE中去(打开IE->;Internet选项->内容->证书) root.p12导入至受信任的根证书颁发机构,client.p12导入至个人 7.访问你的应用 http://ip:8443,如果配置正确的话会出现请求你数字证书的对话框 8.在jsp中取得符合x.509格式的证书 java 代码 - <%
- //获得certificate chain
- X509Certificate[] ca=(X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
- if(ca==null)
- {
- out.println("No cert info!");
- } else {
- String serial=ca[0].getSerialNumber().toString();
- String DN=ca[0].getSubjectDN().toString();
- }
- %>
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!792.entry
切换到SSL再切换回来 原文链接:http://hi.baidu.com/jcjxwxb/blog/item/a4a6f301b4416e06738da598.html Switch to SSL and back again ◆ 问题 怎样在应用程序中使用SSL。 ◆ 背景 大多数组织把信息资料当作他们的最宝贵的资产。保护信息的工作不仅仅是象开发者和管理员似的一个专职的工作而已。信息的保护必须从许多不同的角度实施。数据库,用户界面和商业层只是这些需要你注意的关键领域的某些方面。甚至通往服务器机房的门都必须被锁住。更重要的是,一旦这些数据经由因特网离开了这些建筑物,它的安全就会被一些很有才能的在网络上用电子手段刺探的人危及。 在本方法中,我们示范怎样在容器中启动Secure Socket Layer (SSL)。另外,我们为你介绍Struts SSL 的扩展,HTTP/HTTPS切换库——一个允许你声明哪些Action启用了SSL 的程序包。这个很好用的程序包允许你在SSL-激活的Action和禁止的Action之间很容易地转换。 进入本方法之前,让我们涉及一些SSL 基础。SSL原来是一个由Netscape 开发的网络协议,来提供认证和传输保密性。SSL 协议属于TCP/IP之上的网络层,但是低于应用协议,比如HTTP ,LDAP和IMAP。在传送前客户端检验服务器的身份。 类似的,服务器端也可能可选地检验客户端的身份。为了阻断数字偷听者的努力,这两个主机就一个密码算法达成一致协议,通常叫做一个密码,来加密和解密传送的数据。除提供机密性之外,SSL 对传送过程进行审计,来保证传输的数据在途中未被篡改。整个SSL 的工作方式超出了这个方法的范围,但是我们鼓励你利用一些在资源部分提到的资源探索SSL。 大多数容器,包括Tomcat ,提供SSL 服务。在你容器上启动SSL 确保你的通讯是安全的和不被篡改的。容器能通过检查URL 方案识别出SSL 通讯。URL 方案是URL 中冒号前面的那部分。HTTP 非保密协议是用冒号之前的“HTTP”识别的。例如,http://127.0.0.1指出一个非保密http 协议。一个“s”放置在http 后,来表明该请求希望使用SSL 传送。例如,https://127.0.0.1是用于一个SSL http 请求的适当的URL。 许多应用程序兼具SSL 和非SSL连接。这里面的挑战是当你要一个安全通信的时候,怎样格式化你的连接成为SSL 。作为默认,Struts 链接标签使用与你正在浏览的当前页面相同的URI 方案。所以,如何从一个安全的页面到一个不安全的页面建立一个链接,或者反过来怎么做呢?一个选择是建立你自己的链接标签来写SSL 链接,但是这会迅速地导致维护的负担,只要你需要把链接从一个转变到另一个的任何时候。现在如果你能通过在struts-config.xml 文件中声明来做这个,你会成为一个幸福的露营者了。你可以使用标签来写你的链接,但是一旦情况发生变化,你可以在struts-config.xml 文件中声明性地改变它们。很幸运,那刚好是Struts SSL HTTP/HTTPS 切换扩展库为你做的。在这个方法中,我们展示怎样用Tomcat 设立SSL。然后我们展示怎样在你的应用程序中设立和使用ssl-ext 来加密网上传输的数据。完整的SSL 认证的说明超出了本书的范围;实际上,我们集中在Struts 开发的SSL方面。 ◆ 方法 本方法的实现被分成三个部分。在前边部分我们展示怎样在Tomcat 上启动SSL 。接下来,我们描述究竟需要做什么来完成在你的应用程序中安装SSL-ext。最后,我们在代码中应用SSL-ext。现在开始! 步骤1:在Tomcat 上启动SSL 1 从http://java.sun.com/products/jsse/下载并安装JSSE 1.0.2(或者更新的版本)。此网址告诉你安装JSSE必须知道的所有动作。注意,Java 2 SDK Standard Edition v1.4已经预先捆绑了JSSE。如果你正在使用JDK v1.4,你可以跳个过这个步骤。 2 创建一个证书keystore(密钥仓库)。键入以下命令之一: 对Windows: %JAVA_HOME%\bin\keytool -genkey -alias tomcat -keyalg RSA -keystore D:\server.keystore -validity 365 对UNIX: $JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA 你会被问到许多问题。作为练习目的,仅仅键入对你来说有意义的无论什么都可以。当要实现到一个受控环境中时,找你的系统管理员。 3 从conf/server.xml 中取消SSL HTTP/1.1 Connector 的注释。该被取消注释的连接器应该看起来像这样(特别地,记下端口号,随后必须知道): <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="D:/server.keystore" keystorePass="changeit" /> <!-- Define an AJP 1.3 Connector on port 8009 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> 4 重新启动Tomcat。用浏览器访问https://127.0.0.1:8443来确定所有动作如设计运行。如果Tomcat 显示该页面,那么就奏效了。 祝贺,你已经在Tomcat 上激活了SSL。 步骤2:为应用程序设置Struts SSL HTTP/HTTPS 切换扩展(SSL-ext) 因为ssl-ext 是一个Struts 扩展库,它的安装与Struts类似就不奇怪了。如果你已经安装了Struts,以下用法说明会似曾相识。 1 从http://sslext.sourceforge.net 下载ssl-ext 包。该包包含一个可工作的样例应用程序。把下载包Unzip 到Tomcat webapps 目录。在以下步骤中,你要从样例应用程序复制许多文件来创建你自己的ssl-ext 应用程序。 2 把sslext.jar 文件从样例应用程序复制到WEB-INF/lib 目录中。 3 把sslext.tld 文件从样例应用程序复制到你的WEB-INF/lib 目录中。 4 把以下片断和其他taglib一起放在web.xml 文件中: <taglib> <taglib-uri>/WEB-INF/sslext.tld</taglib-uri> <taglib-location>/WEB-INF/sslext.tld</taglib-location> </taglib> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <!-- 配置ssl的tld --> <jsp-config> <taglib> <taglib-uri>/WEB-INF/sslext.tld</taglib-uri> <taglib-location>/WEB-INF/sslext.tld</taglib-location> </taglib> </jsp-config> 5 把插件标签添加到struts-config.xml 文件中。特别记下httpsPort 属性。它必须与用来配置Tomcat 的相同(步骤1,说明3)。如你可能预期的,该httpPort 属性应该和在conf/server.xml 中找到的Tomcat 配置相配。下列两个定义的值是Tomcat 的值。 <plug-in className="org.apache.struts.action.SecurePlugIn"> <set-property property="httpPort" value="8080"/> <set-property property="httpsPort" value="8443"/> <set-property property="enable" value="true"/> </plug-in> 做完这些,你随时可以开始使用ssl-ext了。 步骤3:使用ssl-ext 建立一个应用程序 我们通过建立一个应用程序来示范ssl-ext 运转。在这个步骤中,我们展示ssl-ext 是如何被用来为指向ssl 安全页面的链接格式化URL 的。相反的,我们示范为指向普通的非SSL页面的链接格式化URL。我们将知道给页面提供安全保障是通过对struts-config.xml 做一个小的改变完成的。清单7.6 展示struts-config.xml 对应一个示例应用程序的动作映射。 清单7.6 Struts-config.xml <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"> <struts-config> <global-forwards type="org.apache.struts.action.ActionForward"> <forward name="unsecured" path="/unsecured.do"/> <forward name="secured" path="/secured.do"/> <forward name="menu" path="/menu.do"/> </global-forwards> <form-beans> <form-bean name="dummyForm" type="com.strutsrecipes.ssl.forms.Dummy" /> </form-beans> <action-mappings type="org.apache.struts.config.SecureActionConfig"> <action path="/menu" type="org.apache.struts.actions.ForwardAction" parameter="/WEB-INF/pages/menu.jsp"> <set-property property="secure" value="false"/> </action> <action path="/unsecured" type="org.apache.struts.actions.ForwardAction" parameter="/WEB-INF/pages/unsecured.jsp"> <set-property property="secure" value="false"/> </action> <action path="/secured" type="org.apache.struts.actions.ForwardAction" parameter="/WEB-INF/pages/secured.jsp"> <set-property property="secure" value="true"/> </action> <action path="/securesubmit" type="org.apache.struts.actions.ForwardAction" name="dummyForm" parameter="/WEB-INF/pages/securesubmit.jsp"> <set-property property="secure" value="true"/> </action> <action path="/unsecuresubmit" type="org.apache.struts.actions.ForwardAction" name="dummyForm" parameter="/WEB-INF/pages/unsecuresubmit.jsp"> <set-property property="secure" value="false"/> </action> </action-mappings> <plug-in className="org.apache.struts.action.SecurePlugIn"> <set-property property="httpPort" value="8080"/> <set-property property="httpsPort" value="8443"/> <set-property property="enable" value="true"/> </plug-in> </struts-config> 要用SSL 使一个Action 安全,把一个<set-property>标签嵌套在Action 标签内。 该property 属性的值永远是secure。该值属性的true 值表明我们需要URL通过设置URI 方案“https”来发出一个SSL 请求。类似的,false 值表明该页面是普通的不安全的页面;该URI 方案应该是“http”。一个any 值默认表示采用当前页的方案。虽然你可以使 用一个普通的Struts 链接标签达到相同效果,但是该any 值让你可以通过修改Struts-config 文件把它改成true 或者false 。 因为该安全属性不被默认动作映射支持,我们需要在(处)重载它。清单7.7示范ssl-ext 链接标签如何被用来产生一个协议格式的URL 。 清单7.7 menu.jsp: JSP 使用 ssl-ext 链接标签 <%@ page language="java" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ taglib uri="/WEB-INF/sslext.tld" prefix="sslext"%> <html:html> <h1>SSL: Menu</h1> <h2>Links</h2> <br><sslext:link forward="unsecured">unsecured</sslext:link> <br><sslext:link forward="secured">secured</sslext:link> <h2>submit to secured</h2> <sslext:form action="/securesubmit" > <br><html:text property="name" value=""/> <html:submit/> </sslext:form> <h2>submit to unsecured</h2> <sslext:form action="/unsecuresubmit" > <br><html:text property="name" value=""/> <html:submit/> </sslext:form> </html:html> 指向安全页面的链接前缀https ,指向不安全的的页面的链接被格式化成通常的http 。例如,在处的链接显示为“不安全的”http://127.0.0.1:8080/ssl/unsecured.do,而在处的链接显示为“安全的”https://127.0.0.1:8443/ssl/secured.do。 在清单7.7中最显著的结果是我们从未指定页面使用SSL,是否是安全的。该信息是定义在struts-configxml 文件中的。在清单7.7中处的链接标签映射到清单7.6的处。相似的,处映射到处,处映射到处,处映射到处。当写URL 的协议段的时候,该ssl:ext 链接标签查阅struts-config.xml 文件。要使用该ssl-ext taglib,你需要像我们在处已经做的那样声明它。 提交工作以大体上相同的方式进行,只是你必须从ssl-ext 名字空间(处)(处)使用表单标签。 到这里,应用ssl-ext 的过程在上述步骤中得到完全解决。 ◆ 讨论 因为步骤1和步骤2是显而易见的,我们无需回顾那些部分。步骤3更有意思得多,并且值得仔细考察。JSP 的工作很简单,不考虑sslext 名字空间的话,它看起来几乎无异于任何Struts JSP 。代替用Struts 链接标签写链接的是我们使用sslext 名字空间当中相同的标签。在这些场景后面,这些标签是使用动作映射来把URI 方案设置为http 或者https 的。要用https 写URL,把动作映射上的secure 属性设置为true。要把URL 写成“http”,把它设置为false。提交的过程也是一样,只是你使用sslext 名字空间的表单标签而不是Struts 的html 名字空间。 还有一个标签我们尚未讨论过。pageScheme标签使用secure属性 来强制一个到http 或者https 的重定向。例如,一个到<sslext:pageScheme secure="false">的页面的https 请求重定向该请求到http,而不管实际上该请求是用https发起的。 如果没有ssl-ext,用以保证URL 使用正确前缀方案的工作将会更加困难。代替通过在JSP 中修整每个ssl 链接来处理这个问题的是,我们可以从struts-conf. xml 文件中来管理它。如此,维护性方面的报偿是很客观的。例如,如果我们有到相同Struts Action 的20个链接,那么为了改变链接以使用SSL,必须对20个JSP 做改变。同等的改变在ssl-ext 下只需要对struts-config.xml的仅仅一个改动!犯太多罪过的是人,避免错误的是神。 最优方法 审慎地使用SSL——SSL 是一个认证和使应用程序安全的已被证实的、可靠的、灵活的,并且流行的办法,但是你应该仅当需要时才使用它。所有SSL 的传送都加密并解密数据。取决于选择的密码,这些运算会影响应用程序的性能。 ◆ 参考 n [SSLDOC] SSL Overview n [SSLEXT] SSL Extension for Struts HTTP/HTTPS switching n [SSLSPEC] SSL Specification n [SSLTOM] SSL Tomcat Configuration n [URI] W3 URI Specification 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!791.entry
【转自】http://caterpillar.onlyfun.net/Gossip/JSPServlet/JSPServlet.htm 雖 是所謂的編程式安全,不過還是搭配之前所談及的宣告安全環境設定來達成,您可以從HttpServletRequest的 getUserPrincipal()來取得代表登入使用者的Principal,或是使用getRemoteUser()方法來取得登入的使用者名稱。 一個搭配Role設定的方法是isUserInRole(),若您的使用者已經通過驗證,可以藉由這個方法於程式中判定登入的使用者其Role為何,並進 一步決定可使用的資源,像是可以決定使用者是否出現進階使用者選單、試用帳號是否過期之類,這是單純對URL作防護的設定所無法達到的,例如: ... if(request.isUserInRole("manager")) { // 顯示管理者選單 } else { // 顯示一般使用者選單 } ... 若您事先無法決定Role名稱,則可以透過設置<security-role-ref>,將程式中的Role名稱,連結至 <security-role>的Role名稱,例如:
... <servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>onlyfun.caterpillar.HelloServlet</servlet-class> <security-role-ref> <role-name>manager</role-name> <role-link>admin</role-link> </security-role-ref> </servlet>
<servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/HelloServlet</url-pattern> </servlet-mapping>
<security-role> <role-name>admin</role-name> <role-name>normal</role-name> </security-role> ... 如此一來,您的程式中的manager,實際上就會對應至admin的Role名稱,如此,就不用擔心在程式中寫死Role名稱。
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!789.entry
【转自】http://caterpillar.onlyfun.net/Gossip/JSPServlet/JSPServlet.htm Http Basic Authentication 會讓瀏覽器出現對話方塊,以供使用者輸入名稱與密碼,無法自行設計登入畫面,若要結束目前會話階段,則要關閉瀏覽器。 您可以在web.xml中設定基於表單的驗證方式,以 使用宣告式安全(Http Basic Authentication) 中的例子來說,可以修改為: <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <security-role> <role-name>foo</role-name> </security-role> <security-constraint> <display-name>SecurityConstraint</display-name> <web-resource-collection> <web-resource-name>Secret Information</web-resource-name> <url-pattern>/secure/info.jsp</url-pattern> </web-resource-collection> <auth-constraint> <role-name>foo</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/pages/login.jsp</form-login-page> <form-error-page>/pages/error.jsp</form-error-page> </form-login-config> </login-config> </web-app> 在<login-config>中,修改驗證方式為FORM,並設定了登入表單的頁面所在,您可以設計一個login.jsp:
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>請登入</title> </head> <body> <h2>請登入</h2><br> <form action="j_security_check" method="POST"> Username: <input type="text" name="j_username" value="" /> <br /> Password: <input type="password" name="j_password" value="" /> <br /> <input type="submit" value="login" /> </form> </body> </html> 當使用者請求受保護的URL時,會轉至login.jsp,發送表單的動作網址為j_security_check,而使用者 名稱與密碼,則必須使用j_username與j_password請求參數發送,若驗證正確,則會將流程轉至原先請求的位址,若失敗,則流程會轉至所設 定的錯誤網頁。 如果要令此次登入失效,則設計一個令HttpSession執行invaldate()方法的請求即可。 如果您的Web伺服器作好支援SSL的設定,則您可以在web.xml中設定使用SSL,以上例來說,您可以在web.xml中增加設定:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <security-role> <role-name>foo</role-name> </security-role> <security-constraint> <display-name>SecurityConstraint</display-name> <web-resource-collection> <web-resource-name>Secret Information</web-resource-name> <url-pattern>/secure/info.jsp</url-pattern> </web-resource-collection> <auth-constraint> <role-name>foo</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/pages/login.jsp</form-login-page> <form-error-page>/pages/error.jsp</form-error-page> </form-login-config> </login-config> </web-app> 如此,當流程轉發至表單網頁,直至使用者名稱、密碼送出,都會以SSL的方式進行加密傳送。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!788.entry
【转自】http://caterpillar.onlyfun.net/Gossip/JSPServlet/JSPServlet.htm 要 防護應用程式的資源,驗證(Authentication)與授權(Authorization)是基本需求。 驗證基本上就是識別登入系統的使用者,是否為系統所允許的身份,是否如其所宣告的身份。授權則是對資源加以管理保護,針對請求資源的使用者,檢查其是否具 備足夠的權限。 在Java EE中,容器提供驗證、授權服務,透過適當的組態設定或API,您可以讓容器為您管理大部份的驗證與授權,要運用Java EE所提供的驗證、授權服務,基本上您必須了解幾個概念名詞: 中文稱之為「域」 或「範圍」,在Java EE的規範之中,Realm指的是身份驗證資料的來源,Realm可能是記憶體、檔案、憑證、資料庫、網路(如LDAP)等。 例如若為Web容器部份,Tomcat預設是將身份驗證資料儲存於tomcat-users.xml檔案之中,在啟動Tomcat之後,將該檔案載入記憶 體作為Memory Realm,您也可以將之改為JDBC Realm。Glassfish(原 Sun Java System Application Server)的Realm可以是 File Realm(一般使用者)、Admin Realm(管理者)、 Certificate realm等,您可以在Glassfish管理介面中加以設定。 在系統上會有 User,而在多人共用的系統會有不同的權限,為了方便管理使用者的權限,通常會定義Group,將User歸類於某些Group,您可以直接將權限設定 給Group,而Group下的User就擁有該權限。 然而在設計應用程式時,並無法事先得知系統上會有哪些Group,所以您無法直接在應用程式中使用Group名稱 來設定資源的權限,因此在Java EE的規範中,是定義資源可以被哪些Role存取,至於如何將系 統上的Group對應至應用程式的Role不在Java EE的規範之中,而是依伺服器廠商的實作而有所不同。 在設計應用程式 時,因無法事先得知將被部署至的系統上,會有哪些User,因此在Java EE中,登入系統的使用者是定義為Principal,您是依據Proincipal於應用程式中作識別驗證,如何將系統上的User對應至應用程式的 Principal不在Java EE的規範之中,而是依伺服器廠商的實作而有所不同。 例如,Tomcat可以在tomcat-users.xml中設定Role與User的對應,而Glassfish,則可以在sun-web.xml、 sun-ejb-jar.xml、sun-application.xmll等檔案中設定Role與Group的對應。
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!787.entry
【转自】http://caterpillar.onlyfun.net/Gossip/JSPServlet/JSPServlet.htm Web容器所提供的宣告安全管理,主要是針對URL來作防護,將您所想要經過驗證、授權才可以存取的Web目錄、網頁或檔案等,在web.xml中作設定,當有使用者想要存取時,就必須輸入名稱與密碼進行驗證,並且必須被授于正確的權限才可存取。 例如若Web應用程式的/serure/info.jsp僅能是foo的Role方可存取,則您可以在web.xml中設定如下: <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <security-role> <role-name>foo</role-name> </security-role> <security-constraint> <display-name>SecurityConstraint</display-name> <web-resource-collection> <web-resource-name>Secret Information</web-resource-name> <url-pattern>/secure/info.jsp</url-pattern> </web-resource-collection> <auth-constraint> <role-name>foo</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>file</realm-name> </login-config> </web-app> <security-role>讓您設定這個Web應用程式中將參與的角色,若有多個角色,則可以用多個<security>加以設定。 在需要防護的URL部份,是在<url-patern>中指定,指定的方式從Web應用程式根目錄開始,若您想防護整個Web應用程式,則可以使用/*來指定,若有多個URL需要防護,則可以使用多個<url-pattern>。 <auth-constraint>中指定哪個Role可以存取指定URL的Role,若有多個Role則使用多個<role-name>來指定。 在<login-config>中,<auth-method>用來指定驗證使用者的方式,指定BASIC的話將授用Http Basic Authentication,也就是丟出「WWW-Authenticate: Basic realm=""」這樣的回應標頭給瀏覽器,瀏覽器將顯示一個輸入對話方塊,要求使用者輸入名稱與密碼,若有輸入名稱與密碼,則瀏覽器會將之以 BASE64方式編碼,以「Authorization: Basic 編碼內容」的請求標頭傳給伺服器。 輸入名稱與密碼在伺服端可能通過驗證,下一步是檢查登入的使用者其對應的Role是否有足夠的權限存取資源,若否則會顯示403 Forbidden。 使用者與Role的對應,在Tomcat上,可以編輯tomcat-users.xml來設定,例如:
... <tomcat-users> <role rolename="foo"/> <user username="caterpillar" password="123456" roles="foo"/> ... </tomcat-users>
若是在Glassfish上,可以在Glassfish的管理介面上新增新的使用者並設定其群組,假設分別為caterpillar與orzGroup,則您可以在WEB-INF下編輯sun-web.xml,設定Role與Group的對應:
... <security-role-mapping> <role-name>foo</role-name> <group-name>orzGrooup</group-name> </security-role-mapping> ...
在設定<web-resource-collection>時,預設是所有的HTTP請求都需要經過驗證與授權,您可以使用<http-method>來設定哪些HTTP請求才需要經過驗證與授權,例如設定GET與POST需要經過驗證與授權:
... <web-resource-collection> <web-resource-name>Secret Information</web-resource-name> <url-pattern>/secure/info.jsp</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> ...
要注意的是,一但設定<http-method>,表示有指定的HTTP請求才需要驗證與授權,但沒有被指定的則不需要,也就是說,若以上面的指定而言,GET、POST是需要驗證與授權的,但HEAD、TRACE等方法則不需要。
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!785.entry
StrutsFileUpload 文件上传的简单范例 HTML HTML页面需要做两件事情,首先,表单需要指定enctype="multipart/form-dataand",其次需要一个类型为file的<input>表单控件。 <form name="myForm" method="post" action="/mywebapp/uploadMyFile.do" enctype="multipart/form-data"> Select File: <input type="file" name="myFile"> </br> <input type="submit" value="Upload File"> </form> JSP 上面的HTML标签用Struts标签代替就是以下代码: <html:form action="/uploadMyFile.do" enctype="multipart/form-data"> Select File: <html:file property="myFile"> </br> <html:submit value="Upload File"/> </html:form> ActionForm 这个ActionForm需要一个FormFile类型的字段。 一般的ActionForm import org.apache.struts.upload.FormFile; public class MyActionForm extends ActionForm { private FormFile myFile; public void setMyFile(FormFile myFile) { this.myFile = myfile; } public FormFile getMyFile() { return myFile; } } 动态ActionForms 在struts-config.xml文件中写上: <form-bean name="myForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="myFile" type="org.apache.struts.upload.FormFile"/> </form-bean> 在Action中需要怎么写呢? 其实没什么特殊的,就象和得到其他属性一样,从ActionForm中得到FormFile属性,得到后可以随意进行处理。比如我们可以从FileForm中得到文件名,文件大小,文件内容 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { MyActionForm myForm = (MyActionForm)form; // Process the FormFile FormFile myFile = myForm.getMyFile(); String contentType = myFile.getContentType(); String fileName = myFile.getFileName(); int fileSize = myFile.getFileSize(); byte[] fileData = myFile.getFileData(); ... } 文件上传的配置 在struts-config.xml的<controller>element中可以设置如下参数来配置文件上传: bufferSize - 处理文件上传的缓冲区大小,单位是字节。 默认是4096byte。 maxFileSize - 允许上传文件的大小。可以使用K,M,G为单位。 默认是250M。 multipartClass - muiltpart请求处理器类的全局标识名。默认是org.apache.struts.upload.CommonsMultipartRequestHandler tempDir - 处理文件上传的临时目录。 还有一种可选的文件上传插件的方式可提供使用,那就是实现 org.apache.struts.upload.MultipartRequestHandler接口。 可以在struts-config.xml的<controller>的multipartClass 来指定这个实现给接口的类。 ==================================== StrutsFileDownload Struts 1.2.6中推出了新的DownloadAction,用来简化下载操作。 实现DownloadAction 我们需要扩展org.apache.struts.actions.DownloadAction并实现 getStreamInfo()方法。如果我们要更改默认的缓冲大小,我们也可以覆盖 getBufferSize()方法。 实现getStreamInfo() 方法 getStreamInfo() 方法返回一个StreamInfo对象- 它是DownloadAction类的内 部类,其实是个内部接口。DownloadAction为这个接口提供了两个具体的静态内 部实现类: FileStreamInfo - 简化从磁盘系统下载文件。需要连同content type传入一个java.io.File对象到构造方法中。 ResourceStreamInfo - 简化从web应用资源下载文件。需要传入ServletContext,路径以及content type 到它的构造方法中。 在下面的例子中,我们还提供了一个以Byte array方法实现StreamInfo接口的代 码。 实现getBufferSize() 方法 DownloadAction默认返回4096byte的缓冲区我们可以覆盖这个方法来自定义用 来传输文件的缓冲区大小 范例 下面有三个例子: 使用文件 使用web应用资源 使用byte array FileStreamInfo范例 DownloadAction使用文件的例子。这个范例从struts-config.xml的action mapping的parameter属性来得到文件名。 import java.io.File; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.actions.DownloadAction; public class ExampleFileDownload extends DownloadAction{ protected StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // Download a "pdf" file - gets the file name from the // Action Mapping's parameter String contentType = "application/pdf"; File file = new File(mapping.getParameter()); return new FileStreamInfo(contentType, file); } } ResourceStreamInfo范例 DownloadAction使用web应用资源的范例。这个范例从struts-config.xml的 action mapping的parameter属性来得到web应用资源的路径。 import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.actions.DownloadAction; public class ExampleResourceDownload extends DownloadAction { protected StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // Download a "jpeg" file - gets the file name from the // Action Mapping's parameter String contentType = "image/jpeg"; String path = mapping.getParameter(); ServletContext application = servlet.getServletContext(); return new ResourceStreamInfo(contentType, application, path); } } Byte Array 范例 DownloadAction使用字节数组(byte array)的范例。 这个例子创建了一个实现了StreamInfo接口的ByteArrayStreamInfo内部类。 import java.io.IOException; import java.io.InputStream; import java.io.ByteArrayInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.actions.DownloadAction; public class ExampleByteArrayDownload extends DownloadAction { protected StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // Download a "pdf" file String contentType = "application/pdf"; byte[] myPdfBytes = null;// Get the bytes from somewhere return new ByteArrayStreamInfo(contentType, myPdfBytes); } protected class ByteArrayStreamInfo implements StreamInfo { protected String contentType; protected byte[] bytes; public ByteArrayStreamInfo(String contentType, byte[] bytes) { this.contentType = contentType; this.bytes = bytes; } public String getContentType() { return contentType; } public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(bytes); } } } 在WEB页面上使用DownloadAction 最大的疑惑是我么如何使用这个Action? 需要做两件事情: 和任何Struts的action一样,需要在struts-config.xml中进行配置。 在WEB页面中使用它对文件进行连接 下面是struts-config.xml配置的一个例子: <action path="/downloadMyPdfFile" type="myPackage.ExampleFileDownload" parameter="/foo/bar.pdf"> <action path="/downloadMyImage" type="myPackage.ExampleResourceDownload" parameter="/images/myImage.jpeg"> 那么在我们的JSP页面,可以使用类似下面的例子: <html:img action="downloadMyImage" alt="My Image" height="400" width="400"/> <html:link action="downloadMyPdfFile">Click Here to See the PDF</html:link> 注意:我们可能要将struts配置文件中<controller>属性的nocache值设置为false。如果设置为true,可能在IE上不能成功下载文件,但是在Firefox和Safari上工作正常。 <controller contentType="text/html;charset=UTF-8" locale="true" nocache="false" /> 内容部署(Content Disposition) 设置Content Disposition DownloadAction不能处理content dispositon头部。最简单的方法是在getStreamInfo()方法中设置,比如: public class ExampleFileDownload extends DownloadAction{ protected StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // File Name String fileName = mapping.getParameter(); // Set the content disposition response.setHeader("Content-disposition", "attachment; filename=" + new String(fileName.getBytes("gbk"), "iso8859_1"));// 设置文件名 // Download a "pdf" file - gets the file name from the // Action Mapping's parameter String contentType = "application/pdf"; File file = new File(fileName); return new FileStreamInfo(contentType, file); } } 如果需要文件名做为参数,可能需要首先把文件前面的任何路径信息先清除。 @@ Content Disposition的值 我们可以设置content disposition来下载一个文件或者在浏览器中打开一个文件。 在浏览器中打开文件的例子写法: "inline; filename=myFile.pdf" 下载的例子写法: "attachment; filename=myFile.pdf" 显示图片的话,可以使用content disposition的"inline"选项。 文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!784.entry
天才女程序员离开Google了。新闻上如是说。媒体很关注她的博客内容,她一边称赞在Google的工作,一边转身而去“尝试新鲜事物”。我想到三个词:光荣、风度和潇洒。 看看我们这些人,正如范师傅的名言:同样都是在电脑上写程序滴,差别咋就这么大哩! 你有过样的光荣么?有人称你为天才么?哈,我猜你会说有!小学时常常前三名,中学也得过不少奖状吧!好汉不提当年勇,别提算啦。现在天天绞尽脑汁地在键盘上敲敲打打的,能获得多少认可呢?老板的观点是肯定而且明确的:不管你怎么做,只要实现需求,只看结果,不看过程。说实话,对于大部分需求来说,再笨的人也能找到个办法实现。你真的是天才又怎么样?其实和傻B没什么区别!你要是离职了,没人理你,趁着金融危机正好换个薪水低的。你在博客里写八百遍也白搭,因为本来就没几个人看! 没有光荣的程序员,也谈不上什么风度。别人问你在以前的公司做的怎么样呀?傻一点的就把自己的想法老实说了,什么这公司这也不好,那也不好。这是实话呀,要是感觉好的话你怎么会辞职呢?可你说实话人家就觉得你这人不好!你只好说以前的公司挺好的,然后面试人员很怀疑地看着你,问你离职的理由。这叫啥风度? 更别说什么潇洒了!也许你真的曾有过追求与理想,也许你真的炒过自己的老板,现在你还这样么?我有个非常好的朋友常对我引用某个作家话:我们总是在不知不觉中变得世俗。理想值得追求,但她太遥远。生活是实在的。没有工作就没有钱,没有钱就没法过日子。天天吃饭都得要钱,用不完的还要存下来,等着交给那些贪婪无度的房地产开发商。潇洒?潇什么洒!老老实实写你的程序去吧! 偶不系天才,偶也不系女滴,偶写程序没有光荣,偶跳槽没有风度,偶的生活一点也不潇洒。真是太让人失望了! 这虽然是事实,但偶不想这样想! 偶长大后虽然就不天才了,但偶绝对会写程序;偶不是女滴,正好有体力加班干活;偶写程序无人欣赏,但偶挣钱养家孝敬父母抚养儿女,也很光荣;偶跳槽虽然只为多点工资,但也算好合好散好来好去,虽没风度但也不失体面;你说我天天加班努力表现不潇洒,哼,虽然我不叫阿Q,可我偏说我潇洒,我潇洒,我就潇洒! 我们变不成天才女程序员了,但我们一定要快乐地活着! 文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!781.entry
LDAP的英文全称是Lightweight Directory Access Protocol,一般都简称为LDAP。它是基于X.500标准的,但是简单多了并且可以根据需要定制。与X.500不同,LDAP支持TCP/IP,这对访问Internet是必须的。LDAP的核心规范在RFC中都有定义,所有与LDAP相关的RFC都可以在LDAPman RFC网页中找到。现在LDAP技术不仅发展得很快而且也是激动人心的。在企业范围内实现LDAP可以让运行在几乎所有计算机平台上的所有的应用程序从LDAP目录中获取信息。LDAP目录中可以存储各种类型的数据:电子邮件地址、邮件路由信息、人力资源数据、公用密匙、联系人列表,等等。通过把LDAP目录作为系统集成中的一个重要环节,可以简化员工在企业内部查询信息的步骤,甚至连主要的数据源都可以放在任何地方。 LDAP目录的优势 如果需要开发一种提供公共信息查询的系统一般的设计方法可能是采用基于WEB的数据库设计方式,即前端使用浏览器而后端使用WEB服务器加上关系数据库。后端在Windows的典型实现可能是Windows NT + IIS + Acess数据库或者是SQL服务器,IIS和数据库之间通过ASP技术使用ODBC进行连接,达到通过填写表单查询数据的功能; 后端在Linux系统的典型实现可能是Linux+ Apache + postgresql,Apache和数据库之间通过PHP3提供的函数进行连接。使用上述方法的缺点是后端关系数据库的引入导致系统整体的性能降低和系统的管理比较繁琐,因为需要不断的进行数据类型的验证和事务的完整性的确认;并且前端用户对数据的控制不够灵活,用户权限的设置一般只能是设置在表一级而不是设置在记录一级。 目录服务的推出主要是解决上述数据库中存在的问题。目录与关系数据库相似,是指具有描述性的基于属性的记录集合,但它的数据类型主要是字符型,为了检索的需要添加了BIN(二进制数据)、CIS(忽略大小写)、CES(大小写敏感)、TEL(电话型)等语法(Syntax),而不是关系数据库提供的整数、浮点数、日期、货币等类型,同样也不提供象关系数据库中普遍包含的大量的函数,它主要面向数据的查询服务(查询和修改操作比一般是大于10:1),不提供事务的回滚(rollback)机制,它的数据修改使用简单的锁定机制实现All-or-Nothing,它的目标是快速响应和大容量查询并且提供多目录服务器的信息复制功能。 现在该说说LDAP目录到底有些什么优势了。现在LDAP的流行是很多因数共同作用的结果。可能LDAP最大的优势是:可以在任何计算机平台上,用很容易获得的而且数目不断增加的LDAP的客户端程序访问LDAP目录。而且也很容易定制应用程序为它加上LDAP的支持。 LDAP协议是跨平台的和标准的协议,因此应用程序就不用为LDAP目录放在什么样的服务器上操心了。实际上,LDAP得到了业界的广泛认可,因为它是Internet的标准。产商都很愿意在产品中加入对LDAP的支持,因为他们根本不用考虑另一端(客户端或服务端)是怎么样的。LDAP服务器可以是任何一个开发源代码或商用的LDAP目录服务器(或者还可能是具有LDAP界面的关系型数据库),因为可以用同样的协议、客户端连接软件包和查询命令与LDAP服务器进行交互。与LDAP不同的是,如果软件产商想在软件产品中集成对DBMS的支持,那么通常都要对每一个数据库服务器单独定制。不象很多商用的关系型数据库,你不必为LDAP的每一个客户端连接或许可协议付费 大多数的LDAP服务器安装起来很简单,也容易维护和优化。 LDAP服务器可以用“推”或“拉”的方法复制部分或全部数据,例如:可以把数据“推”到远程的办公室,以增加数据的安全性。复制技术是内置在LDAP服务器中的而且很容易配置。如果要在DBMS中使用相同的复制功能,数据库产商就会要你支付额外的费用,而且也很难管理。 LDAP允许你根据需要使用ACI(一般都称为ACL或者访问控制列表)控制对数据读和写的权限。例如,设备管理员可以有权改变员工的工作地点和办公室号码,但是不允许改变记录中其它的域。ACI可以根据谁访问数据、访问什么数据、数据存在什么地方以及其它对数据进行访问控制。因为这些都是由LDAP目录服务器完成的,所以不用担心在客户端的应用程序上是否要进行安全检查。 LDAP(Lightweight Directory Acess Protocol)是目录服务在TCP/IP上的实现(RFC 1777 V2版和RFC 2251 V3版)。它是对X500的目录协议的移植,但是简化了实现方法,所以称为轻量级的目录服务。在LDAP中目录是按照树型结构组织,目录由条目(Entry)组成,条目相当于关系数据库中表的记录;条目是具有区别名DN(Distinguished Name)的属性(Attribute)集合,DN相当于关系数据库表中的关键字(Primary Key);属性由类型(Type)和多个值(Values)组成,相当于关系数据库中的域(Field)由域名和数据类型组成, 只是为了方便检索的需要,LDAP中的Type可以有多个Value,而不是关系数据库中为降低数据的冗余性要求实现的各个域必须是不相关的。LDAP中条目的组织一般按照地理位置和组织关系进行组织,非常的直观。LDAP把数据存放在文件中,为提高效率可以使用基于索引的文件数据库,而不是关系数据库。LDAP协议集还规定了DN的命名方法、存取控制方法、搜索格式、复制方法、URL格式、开发接口等 LDAP对于这样存储这样的信息最为有用,也就是数据需要从不同的地点读取,但是不需要经常更新。 例如,这些信息存储在LDAP目录中是十分有效的: l 公司员工的电话号码簿和组织结构图 l 客户的联系信息 l 计算机管理需要的信息,包括NIS映射、email假名,等等 l 软件包的配置信息 l 公用证书和安全密匙 什么时候该用LDAP存储数据 大多数的LDAP服务器都为读密集型的操作进行专门的优化。因此,当从LDAP服务器中读取数据的时候会比从专门为OLTP优化的关系型数据库中读取数据快一个数量级。也是因为专门为读的性能进行优化,大多数的LDAP目录服务器并不适合存储需要需要经常改变的数据。例如,用LDAP服务器来存储电话号码是一个很好的选择,但是它不能作为电子商务站点的数据库服务器。 如果下面每一个问题的答案都是“是”,那么把数据存在LDAP中就是一个好主意。 l 需要在任何平台上都能读取数据吗? l 每一个单独的记录项是不是每一天都只有很少的改变? l 可以把数据存在平面数据库(flat database)而不是关系型数据库中吗?换句话来说,也就是不管什么范式不式的,把所有东西都存在一个记录中(差不多只要满足第一范式)。 最后一个问题可能会唬住一些人,其实用平面数据库去存储一些关系型的数据也是很一般的。例如,一条公司员工的记录就可以包含经理的登录名。用LDAP来存储这类信息是很方便的。一个简单的判断方法:如果可以把保数据存在一张张的卡片里,就可以很容易地把它存在LDAP目录里。 安全和访问控制 LDAP提供很复杂的不同层次的访问控制或者ACI。因这些访问可以在服务器端控制,这比用客户端的软件保证数据的安全可安全多了。 用LDAP的ACI,可以完成: l 给予用户改变他们自己的电话号码和家庭地址的权限,但是限制他们对其它数据(如,职务名称,经理的登录名, 等等)只有“只读”权限。 l 给予“HR-admins"组中的所有人权限以改变下面这些用户的信息:经理、工作名称、员工号、部门名称和部门号。 但是对其它域没有写权限。 l 禁止任何人查询LDAP服务器上的用户口令,但是可以允许用户改变他或她自己的口令。 l 给予经理访问他们上级的家庭电话的只读权限,但是禁止其他人有这个权限。 l 给予“host-admins"组中的任何人创建、删除和编辑所有保存在LDAP服务器中的与计算机主机有关的信息 l 通过Web,允许“foobar-sales"组中的成员有选择地给予或禁止他们自己读取一部分客户联系数据的读权限。这将允许他们把客户联系信息下载到本地的笔记本电脑或个人数字助理(PDA)上。(如果销售人员的软件都支持LDAP,这将非常有用) l 通过Web,允许组的所有者删除或添加他们拥有的组的成员。例如:可以允许销售经理给予或禁止销售人员改变Web页的权限。也可以允许邮件假名(mail aliase)的所有者不经过IT技术人员就直接从邮件假名中删除或添加用户。“公用”的邮件列表应该允许用户从邮件假名中添加或删除自己(但是只能是自己)。也可以对IP地址或主机名加以限制。例如,某些域只允许用户IP地址以192.168.200.*开头的有读的权限,或者用户反向查找DNS得到的主机名必须为*.foobar.com。 LDAP目录树的结构
LDAP目录以树状的层次结构来存储数据。如果你对自顶向下的DNS树或UNIX文件的目录树比较熟悉,也就很容易掌握LDAP目录树这个概念了。就象DNS的主机名那样,LDAP目录记录的标识名(Distinguished Name,简称DN)是用来读取单个记录,以及回溯到树的顶部。后面会做详细地介绍。 为什么要用层次结构来组织数据呢?原因是多方面的。下面是可能遇到的一些情况: l 如果你想把所有的美国客户的联系信息都“推”到位于到西雅图办公室(负责营销)的LDAP服务器上,但是你不想把公司的资产管理信息“推”到那里。 l 你可能想根据目录树的结构给予不同的员工组不同的权限。在下面的例子里,资产管理组对“asset-mgmt"部分有完全的访问权限,但是不能访问其它地方。 l 把LDAP存储和复制功能结合起来,可以定制目录树的结构以降低对WAN带宽的要求。位于西雅图的营销办公室需要每分钟更新的美国销售状况的信息,但是欧洲的销售情况就只要每小时更新一次就行了。 刨根问底:基准DN LDAP目录树的最顶部就是根,也就是所谓的“基准DN"。基准DN通常使用下面列出的三种格式之一。假定我在名为FooBar的电子商务公司工作,这家公司在Internet上的名字是foobar.com。 o="FooBar, Inc.", c=US (以X.500格式表示的基准DN) 在这个例子中o=FooBar, Inc. 表示组织名,在这里就是公司名的同义词。c=US 表示公司的总部在美国。以前,一般都用这种方式来表示基准DN。但是事物总是在不断变化的,现在所有的公司都已经(或计划)上Internet上。随着 Internet的全球化,在基准DN中使用国家代码很容易让人产生混淆。现在,X.500格式发展成下面列出的两种格式。 o=foobar.com (用公司的Internet地址表示的基准DN) 这种格式很直观,用公司的域名作为基准DN。这也是现在最常用的格式。 dc=foobar, dc=com (用DNS域名的不同部分组成的基准DN) 就象上面那一种格式,这种格式也是以DNS域名为基础的,但是上面那种格式不改变域名(也就更易读),而这种格式把域名:foobar.com分成两部分 dc=foobar, dc=com。在理论上,这种格式可能会更灵活一点,但是对于最终用户来说也更难记忆一点。考虑一下foobar.com这个例子。当foobar.com和gizmo.com合并之后,可以简单的把“dc=com"当作基准DN。把新的记录放到已经存在的dc=gizmo, dc=com目录下,这样就简化了很多工作(当然,如果foobar.com和wocket.edu合并,这个方法就不能用了)。如果LDAP服务器是新安装的,我建议你使用这种格式。再请注意一下,如果你打算使用活动目录(Actrive Directory),Microsoft已经限制你必须使用这种格式。 更上一层楼:在目录树中怎么组织数据 在UNIX文件系统中,最顶层是根目录(root)。在根目录的下面有很多的文件和目录。象上面介绍的那样,LDAP目录也是用同样的方法组织起来的。 在根目录下,要把数据从逻辑上区分开。因为历史上(X.500)的原因,大多数LDAP目录用OU从逻辑上把数据分开来。OU 表示“Organization Unit",在X.500协议中是用来表示公司内部的机构:销售部、财务部,等等。现在LDAP还保留ou=这样的命名规则,但是扩展了分类的范围,可以分类为:ou=people, ou=groups, ou=devices,等等。更低一级的OU有时用来做更细的归类。例如:LDAP目录树(不包括单独的记录)可能会是这样的: dc=foobar, dc=com ou=customers ou=asia ou=europe ou=usa ou=employees ou=rooms ou=groups ou=assets-mgmt ou=nisgroups ou=recipes 单独的LDAP记录 DN是LDAP记录项的名字 在LDAP目录中的所有记录项都有一个唯一的“Distinguished Name",也就是DN。每一个LDAP记录项的DN是由两个部分组成的:相对DN(RDN)和记录在LDAP目录中的位置。 RDN是DN中与目录树的结构无关的部分。在LDAP目录中存储的记录项都要有一个名字,这个名字通常存在cn(Common Name)这个属性里。因为几乎所有的东西都有一个名字,在LDAP中存储的对象都用它们的cn值作为RDN的基础。如果我把最喜欢的吃燕麦粥食谱存为一个记录,我就会用cn=Oatmeal Deluxe作为记录项的RDN。 l 我的LDAP目录的基准DN是dc=foobar,dc=com l 我把自己的食谱作为LDAP的记录项存在ou=recipes l 我的LDAP记录项的RDN设为cn=Oatmeal Deluxe 上面这些构成了燕麦粥食谱的LDAP记录的完整DN。记住,DN的读法和DNS主机名类似。下面就是完整的DN: cn=Oatmeal Deluxe,ou=recipes,dc=foobar,dc=com 举一个实际的例子来说明DN 现在为公司的员工设置一个DN。可以用基于cn或uid(User ID),作为典型的用户帐号。例如,FooBar的员工Fran Smith (登录名:fsmith)的DN可以为下面两种格式: uid=fsmith,ou=employees,dc=foobar,dc=com (基于登录名) LDAP(以及X.500)用uid表示“User ID",不要把它和UNIX的uid号混淆了。大多数公司都会给每一个员工唯一的登录名,因此用这个办法可以很好地保存员工的信息。你不用担心以后还会有一个叫Fran Smith的加入公司,如果Fran改变了她的名字(结婚?离婚?或宗教原因?),也用不着改变LDAP记录项的DN。 cn=Fran Smith,ou=employees,dc=foobar,dc=com (基于姓名) 可以看到这种格式使用了Common Name(CN)。可以把Common Name当成一个人的全名。这种格式有一个很明显的缺点就是:如果名字改变了,LDAP的记录就要从一个DN转移到另一个DN。但是,我们应该尽可能地避免改变一个记录项的DN。 定制目录的对象类型 你可以用LDAP存储各种类型的数据对象,只要这些对象可以用属性来表示,下面这些是可以在LDAP中存储的一些信息: l 员工信息:员工的姓名、登录名、口令、员工号、他的经理的登录名,邮件服务器,等等。 l 物品跟踪信息:计算机名、IP地址、标签、型号、所在位置,等等。 l 客户联系列表:客户的公司名、主要联系人的电话、传真和电子邮件,等等。 l 会议厅信息:会议厅的名字、位置、可以坐多少人、电话号码、是否有投影机。 l 食谱信息:菜的名字、配料、烹调方法以及准备方法。 因为LDAP目录可以定制成存储任何文本或二进制数据,到底存什么要由你自己决定。LDAP目录用对象类型(object classes)的概念来定义运行哪一类的对象使用什么属性。在几乎所有的LDAP服务器中,你都要根据自己的需要扩展基本的LDAP目录的功能,创建新的对象类型或者扩展现存的对象类型。 LDAP目录以一系列“属性对”的形式来存储记录项,每一个记录项包括属性类型和属性值(这与关系型数据库用行和列来存取数据有根本的不同)。下面是我存在LDAP目录中的一部分食谱记录: dn: cn=Oatmeal Deluxe, ou=recipes, dc=foobar, dc=com cn: Instant Oatmeal Deluxe recipeCuisine: breakfast recipeIngredient: 1 packet instant oatmeal recipeIngredient: 1 cup water recipeIngredient: 1 pinch salt recipeIngredient: 1 tsp brown sugar recipeIngredient: 1/4 apple, any type 请注意上面每一种配料都作为属性recipeIngredient值。LDAP目录被设计成象上面那样为一个属性保存多个值的,而不是在每一个属性的后面用逗号把一系列值分开。 因为用这样的方式存储数据,所以数据库就有很大的灵活性,不必为加入一些新的数据就重新创建表和索引。更重要的是,LDAP目录不必花费内存或硬盘空间处理“空”域,也就是说,实际上不使用可选择的域也不会花费你任何资源。 作为例子的一个单独的数据项
让我们看看下面这个例子。我们用Foobar, Inc.的员工Fran Smith的LDAP记录。这个记录项的格式是LDIF,用来导入和导出LDAP目录的记录项。 dn: uid=fsmith, ou=employees, dc=foobar, dc=com objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson objectclass: foobarPerson uid: fsmith givenname: Fran sn: Smith cn: Fran Smith cn: Frances Smith telephonenumber: 510-555-1234 roomnumber: 122G o: Foobar, Inc. mailRoutingAddress: fsmith@foobar.com mailhost: mail.foobar.com userpassword: {crypt}3x1231v76T89N uidnumber: 1234 gidnumber: 1200 homedirectory: /home/fsmith loginshell: /usr/local/bin/bash 属性的值在保存的时候是保留大小写的,但是在默认情况下搜索的时候是不区分大小写的。某些特殊的属性(例如,password)在搜索的时候需要区分大小写。 让我们一点一点地分析上面的记录项。 dn: uid=fsmith, ou=employees, dc=foobar, dc=com 这是Fran的LDAP记录项的完整DN,包括在目录树中的完整路径。LDAP(和X.500)使用uid(User ID),不要把它和UNIX的uid号混淆了。 objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson objectclass: foobarPerson 可以为任何一个对象根据需要分配多个对象类型。person对象类型要求cn(common name)和sn(surname)这两个域不能为空。persion对象类型允许有其它的可选域,包括givenname、telephonenumber,等等。organizational Person给person加入更多的可选域,inetOrgPerson又加入更多的可选域(包括电子邮件信息)。最后,foobarPerson是为Foobar定制的对象类型,加入了很多定制的属性。 uid: fsmith givenname: Fran sn: Smith cn: Fran Smith cn: Frances Smith telephonenumber: 510-555-1234 roomnumber: 122G o: Foobar, Inc. 以前说过了,uid表示User ID。当看到uid的时候,就在脑袋里想一想“login"。 请注意CN有多个值。就象上面介绍的,LDAP允许某些属性有多个值。为什么允许有多个值呢?假定你在用公司的LDAP服务器查找Fran的电话号码。你可能只知道她的名字叫Fran,但是对人力资源处的人来说她的正式名字叫做Frances。因为保存了她的两个名字,所以用任何一个名字检索都可以找到Fran的电话号码、电子邮件和办公房间号,等等。 mailRoutingAddress: fsmith@foobar.com mailhost: mail.foobar.com 就象现在大多数的公司都上网了,Foobar用Sendmail发送邮件和处理外部邮件路由信息。Foobar把所有用户的邮件信息都存在LDAP中。最新版本的Sendmail支持这项功能。 Userpassword: {crypt}3x1231v76T89N uidnumber: 1234 gidnumber: 1200 gecos: Frances Smith homedirectory: /home/fsmith loginshell: /usr/local/bin/bash 注意,Foobar的系统管理员把所有用户的口令映射信息也都存在LDAP中。FoobarPerson类型的对象具有这种能力。再注意一下,用户口令是用UNIX的口令加密格式存储的。UNIX的uid在这里为uidnumber。提醒你一下,关于如何在LDAP中保存NIS信息,有完整的一份RFC。在以后的文章中我会谈一谈NIS的集成。 LDAP复制 LDAP服务器可以使用基于“推”或者“拉”的技术,用简单或基于安全证书的安全验证,复制一部分或者所有的数据。 例如,Foobar有一个“公用的”LDAP服务器,地址为ldap.foobar.com,端口为389。Netscape Communicator的电子邮件查询功能、UNIX的“ph"命令要用到这个服务器,用户也可以在任何地方查询这个服务器上的员工和客户联系信息。公司的主LDAP服务器运行在相同的计算机上,不过端口号是1389。 你可能即不想让员工查询资产管理或食谱的信息,又不想让信息技术人员看到整个公司的LDAP目录。为了解决这个问题,Foobar有选择地把子目录树从主LDAP服务器复制到“公用”LDAP服务器上,不复制需要隐藏的信息。为了保持数据始终是最新的,主目录服务器被设置成即时“推”同步。这些种方法主要是为了方便,而不是安全,因为如果有权限的用户想查询所有的数据,可以用另一个LDAP端口。 假定Foobar通过从奥克兰到欧洲的低带宽数据的连接用LDAP管理客户联系信息。可以建立从ldap.foobar.com:1389到munich-ldap.foobar.com:389的数据复制,象下面这样: periodic pull: ou=asia,ou=customers,o=sendmail.com periodic pull: ou=us,ou=customers,o=sendmail.com immediate push: ou=europe,ou=customers,o=sendmail.com “拉”连接每15分钟同步一次,在上面假定的情况下足够了。“推”连接保证任何欧洲的联系信息发生了变化就 立即被“推”到Munich。 用上面的复制模式,用户为了访问数据需要连接到哪一台服务器呢?在Munich的用户可以简单地连接到本地服务 器。如果他们改变了数据,本地的LDAP服务器就会把这些变化传到主LDAP服务器。然后,主LDAP服务器把这些变化 “推”回本地的“公用”LDAP服务器保持数据的同步。这对本地的用户有很大的好处,因为所有的查询(大多数是读)都在本地的服务器上进行,速度非常快。当需要改变信息的时候,最终用户不需要重新配置客户端的软件,因为LDAP目录服务器为他们完成了所有的数据交换工作。 文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!779.entry
1. 通过EL表达式直接在页面中使用Spring Bean。(这一点称之为:SpringBean被当作LiteBean直接使用) 由于AOM是和Spring紧密集成的,因此,当我们要结合Spring使用的时候,只需要向工程中添加Spring 2.5的一些基本jar包,然后把operamasks-spring.jar这个jar添加到工程中就可以实现在xhtml或者jsp中通过EL表达式来访问Spring Bean。在该方式中,SpringBean可以被注入到LiteBean中。 package com.vv.aom.numbertest; import java.util.Random; public class NumberBean { private int num; public int getNum() { num = new Random().nextInt(10); return num; } public void setNum(int num) { this.num = num; } } | <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd" > <beans> <bean id="numberBean" class="com.vv.aom.numbertest.NumberBean"/> </beans> | <f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:w="http://www.apusic.com/jsf/widget" xmlns:h="http://java.sun.com/jsf/html" xmlns:ajax="http://www.apusic.com/jsf/ajax" renderKitId="AJAX"> <h:form> <p>Generate a number between 0 to 10</p> <h:inputText value="#{numberBean.num}"/><br/> <h:outputText value="Generate" style="text-decoration:underline;cursor:pointer;"> <ajax:action event="onclick"/> </h:outputText> </h:form> </f:view> | 需要注意:如果你需要在页面中通过EL表达式直接使用LiteBean的时候,需要注意你的所有路径下不能出现两个及以上的同名LiteBean,否则系统会自动为你选择路径最短的一个LiteBean。所以仍然推荐使用OperaMask的IoVC来使用LiteBean。 2. 在AOM中定义的LiteBean都可以当作是Spring Bean被使用。 如果希望从Spring上下文中获得一个LiteBean,则必须在Spring配置文件中配置下面的Bean: <bean class="org.operamasks.faces.spring.ManagedBeanConfigurer"/> |
上面的 org.operamasks.faces.spring.ManagedBeanConfigurer是 AOM 默认提供的一个 Spring Bean 定义,必须在Spring中配置此Bean,它的作用是:让 AOM 中的 LiteBean 同样能够被 Spring 所感应到。
如果你使用 Apusic 应用服务器,我们还建议你正确配置 Apusic 应用服务器的 TransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName"> <value>java:/TransactionManager</value> </property> </bean> |
这样,我们在AOM中定义的LiteBean既可以通过applicationContext.getBean(“”)方法来得到,也可以在Spring配置文件中使用LiteBean。
(1)通过spring的applicationContext的getBean方法得到LiteBean:
WebApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(session.getServletContext());
NumberService mySpringBean = (NumberService) appContext.getBean("numberService");
out.print("The bean defined in Spring: " + mySpringBean);
out.print("<br/>");
Object myLiteBean = (Object) appContext.getBean("GenerateNumberBean");
out.print("The bean defined in AOM: " + myLiteBean); |
(2)通过spring-faces-config.xml配置文件使SpringBean和LiteBean协同工作,当然如果要指定该配置文件的个性化位置和名称,可以在web.xml中添加:
<context-param> <param-name>spring.faces.ConfiguarionFiles</param-name> <param-value>/WEB-INF/user-module.xml, /WEB-INF/role-module.xml</param-value> </context-param> |
之后在里面可以这样定义:(完全与Spring IOC的配置无异)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <bean id="compositeBeanAdvice" class="demo.spring.bean.CompositeBeanAdvice"/> <bean id="compositeBeanTarget" class="demo.spring.bean.CompositeBeanImpl" scope="session"> <property name="service" ref="numberService"/> <property name="numberBean" ref="GenerateNumberBean"/> </bean> <bean id="compositeBean" class="org.springframework.aop.framework.ProxyFactoryBean" scope="session"> <property name="proxyInterfaces" value="demo.spring.bean.CompositeBean"/> <property name="interceptorNames"> <list> <value>compositeBeanAdvice</value> </list> </property> <property name="target" ref="compositeBeanTarget"/> </bean> </beans> |
3. SpringBean在被当作LiteBean使用的时候,依然可以获得IoVC支持。这一点是通过在SpringBean中添加@Bind和@Action注解来实现的。例如:
SayHelloBean.java:
package com.vv.aom.numbertest;
import org.operamasks.faces.annotation.Action;
import org.operamasks.faces.annotation.Bind;
import org.operamasks.faces.annotation.ManagedBean;
//@ManagedBean
public class SayHelloBean {
@Bind
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Action
public String sayHello() {
System.out.println("hello " + name);
return null;
}
@Override
public String toString() {
return "MySpringBean";
}
} |
sayHello.xhtml
<f:view xmlns=http://www.w3.org/1999/xhtml xmlns:f="http://java.sun.com/jsf/core"
xmlns:w=http://www.apusic.com/jsf/widget
xmlns:layout="http://www.apusic.com/jsf/layout" renderKitId="AJAX"> <w:page title="spring"> <w:form> <layout:panelGrid columns="2"> <w:textField id="name"/> <w:button id="sayHello"/> </layout:panelGrid> </w:form> </w:page>
</f:view> |
Spring-bean.xml:
<bean id="SayHelloBean" class="com.vv.aom.numbertest.SayHelloBean">
<property name="name" value="Kevin"/>
</bean> | 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!736.entry
Oracle常用的数据库字段类型如下: 字段类型 | 中文说明 | 限制条件 | 其它说明 | CHAR | 固定长度字符串 | 最大长度2000 bytes | | VARCHAR2 | 可变长度的字符串 | 最大长度4000 bytes | 可做索引的最大长度749 | NCHAR | 根据字符集而定的固定长度字符串 | 最大长度2000 bytes | | NVARCHAR2 | 根据字符集而定的可变长度字符串 | 最大长度4000 bytes | | DATE | 日期(日-月-年) | DD-MM-YY(HH-MI-SS) | 经过严格测试,无千虫问题 | LONG | 超长字符串 | 最大长度2G(231-1) | 足够存储大部头著作 | RAW | 固定长度的二进制数据 | 最大长度2000 bytes | 可存放多媒体图象声音等 | LONG RAW | 可变长度的二进制数据 | 最大长度2G | 同上 | BLOB | 二进制数据 | 最大长度4G | | CLOB | 字符数据 | 最大长度4G | | NCLOB | 根据字符集而定的字符数据 | 最大长度4G | | BFILE | 存放在数据库外的二进制数据 | 最大长度4G | | ROWID | 数据表中记录的唯一行号 | 10 bytes ********.****.****格式,*为0或1 | NROWID | 二进制数据表中记录的唯一行号 | 最大长度4000 bytes | NUMBER(P,S) | 数字类型 | P为整数位,S为小数位 | DECIMAL(P,S) | 数字类型 | P为整数位,S为小数位 | INTEGER | 整数类型 | 小的整数 | FLOAT | 浮点数类型 | NUMBER(38),双精度 | REAL | 实数类型 | NUMBER(63),精度更高 | 文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!734.entry
http://zangweiren.javaeye.com/category/34977 2008-08-25 关键字: java 面试题 自增 自减 位运算符 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< 有些运算符在JAVA语言中存在着,但是在实际开发中我们或许很少用到它们,在面试题中却时常出现它们的身影,对于这些运算符的含义和用法,你是否还记得呢? 自增(++)和自减(--)运算符 我们先来回答几个问题吧: int i = 0; int j = i++; int k = --i; 这段代码运行后,i等于多少?j等于多少?k等于多少?太简单了?好,继续: int i = 0; int j = i++ + ... 2008-08-08 关键字: java 面试题 多线程 thread 线程池 synchronized 死锁 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< 线程或者说多线程,是我们处理多任务的强大工具。线程和进程是不同的,每个进程都是一个独立运行的程序,拥有自己的变量,且不同进程间的变量不能共享;而线程是运行在进程内部的,每个正在运行的进程至少有一个线程,而且不同的线程之间可以在进程范围内共享数据。也就是说进程有自己独立的存储空间,而线程是和它所属的进程内的其他线程共享一个存储空间。线程的使用可以使我们能够并行地处理一些事情。线程通过并行的处理给用户带来更好的使用体验,比如你使 ... 2008-07-31 关键字: java 面试题 继承 多态 重载 重写 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< 什么是多态?它的实现机制是什么呢?重载和重写的区别在那里?这就是这一次我们要回顾的四个十分重要的概念:继承、多态、重载和重写。 继承(inheritance) 简单的说,继承就是在一个现有类型的基础上,通过增加新的方法或者重定义已有方法(下面会讲到,这种方式叫重写)的方式,产生一个新的类型。继承是面向对象的三个基本特征--封装、继承、多态的其中之一,我们在使用JAVA时编写的每一个类都是在继承,因为在JAVA语言中,ja ... 2008-07-25 关键字: java 面试题 基本类型 int long boolean float double char 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< 基本类型,或者叫做内置类型,是JAVA中不同于类的特殊类型。它们是我们编程中使用最频繁的类型,因此面试题中也总少不了它们的身影,在这篇文章中我们将从面试中常考的几个方面来回顾一下与基本类型相关的知识。 基本类型共有九种,它们分别都有相对应的包装类。关于它们的详细信息请看下表: [img]http://zangweiren.javaeye.com/upload/picture/pic/18450/8071c6c2-7cfb ... 2008-07-22 关键字: java 面试题 日期 时间 转换 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< 日期和时间的处理不仅在面试题中会考到,在实际项目开发中也是我们经常需要处理的问题,似乎没有哪个项目可以避开它们,我们常常在处理用户的出生年月日、注册日期,订单的创建时间等属性时用到,由此可见其重要性。 java.util.Date类 提到日期和时间,我想大家最先想到应该是java.util.Date类吧。Date类可以精确到毫秒数,这个毫秒数是相对于格林威治标准时间“1970-01-01 00:00:00.000 GMT ... 2008-07-18 关键字: java 面试题 字符串 string 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< 上一次我们已经一起回顾了面试题中常考的到底创建了几个String对象的相关知识,这一次我们以几个常见面试题为引子,来回顾一下String对象相关的其它一些方面。 String的length()方法和数组的length属性 String类有length()方法吗?数组有length()方法吗? String类当然有length()方法了,看看String类的源码就知道了,这是这个方法的定义: public int l ... 2008-07-13 关键字: java 面试题 值传递 引用传递 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< JAVA中的传递都是值传递吗?有没有引用传递呢? 在回答这两个问题前,让我们首先来看一段代码: public class ParamTest { // 初始值为0 protected int num = 0; // 为方法参数重新赋值 public void change(int i) { i = 5; } // 为方法参数重新赋值 public void change(Par ... 2008-07-08 关键字: java 面试题 final finally finalize 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< final、finally和finalize的区别是什么? 这是一道再经典不过的面试题了,我们在各个公司的面试题中几乎都能看到它的身影。final、finally和finalize虽然长得像孪生三兄弟一样,但是它们的含义和用法却是大相径庭。这一次我们就一起来回顾一下这方面的知识。 final关键字 我们首先来说说final。它可以用于以下四个地方: 定义变量,包括静态的和非静态的。定义方法的参数。定义方法。定义 ... 2008-07-03 关键字: java 面试题 继承 变量的覆盖 属性 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< 我们来看看这么一道题: class ParentClass { public int i = 10; } public class SubClass extends ParentClass { public int i = 30; public static void main(String[] args) { ParentClass parentClass = new SubClass() ... 2008-06-30 关键字: java 面试题 string 创建几个对象 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< 我们首先来看一段代码: String str=new String("abc"); 紧接着这段代码之后的往往是这个问题,那就是这行代码究竟创建了几个String对象呢?相信大家对这道题并不陌生,答案也是众所周知的,2个。接下来我们就从这道题展开,一起回顾一下与创建String对象相关的一些JAVA知识。 我们可以把上面这行代码分成String str、=、"abc"和new String()四部分来看待。Strin ... 2008-06-26 关键字: java 面试题 初始化 作者:臧圩人(zangweiren)网址:http://zangweiren.javaeye.com >>>转载请注明出处!<<< 大家在去参加面试的时候,经常会遇到这样的考题:给你两个类的代码,它们之间是继承的关系,每个类里只有构造器方法和一些变量,构造器里可能还有一段代码对变量值进行了某种运算,另外还有一些将变量值输出到控制台的代码,然后让我们判断输出的结果。这实际上是在考查我们对于继承情况下类的初始化顺序的了解。 我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)>(变量、初 ... 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!725.entry
作用域:抛开request,session,globalsession不谈,先说说singleton和prototype。 singleton是默认的作用域,作用域为singleton的Bean在Spring初始化上下文期间就已经初始化,并且全局唯一 作用域为prototype的Bean在Spring初始化上下文期间不进行初始化,只有在getBean()方法被调用时才进行初始化,每调用一次getBean()方法,就生成一个新的Bean实例。 生命周期:一般来讲,如果作用域为默认,除非设置了属性lazy-init="true",一个Bean的实例化是在Spring上下文初始化时进行。 init-mothed属性用于指定Bean初始化时执行的初始化方法,destroy-method用于指定Spring上下文关闭时,单一实例的Bean执行销毁方法。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!723.entry
实例化bean的三种方式: 1。默认构造方法: <bean id=''bean1" class="com.vv.Bean"/> 前提是已经有了Bean.class 2。静态工厂方法: public class BeanFactory { public static Bean createBean(){ return new Bean(); } } <bean id=''bean1" class="com.vv. BeanFactory" factory-mothod=" createBean"/> 3。实例工厂方法: public class BeanFactory {
public Bean createBean(){
return new Bean();
}
} <bean id='' beanFactory" class="com.vv. BeanFactory"/> <bean id="bean1" factory-bean=" beanFactory" factory-mothod=" createBean"/> 备注:默认情况下,如果一个bean的class、factory-bean、factory-method属性都指定后,工厂实例化方法优先 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!722.entry
由于项目需要,写了一个hibernate+datasource的CRUD程序。。。但是部署到测试环境后,修改了context.xml和hibernate.cfg.xml发现程序运行不正常。通过跟踪日志发现Hibernate链接正常,但是datasource连接数据库异常。。。 通过跟踪日志发现是由于配置的JNDI数据源不正常导致的。但是修改了META-INF/context.xml之后仍不能正常工作。在尝试了更换测试环境JDK、更换tomcat、更换jdbc驱动等一系列方法后,终于找到根源,是由于tomcat不能自动同步META-INF/context.xml到%CATALINA_HOME%/conf/Catalina/localhost/%appname%.xml导致的。。。。 经过修改%CATALINA_HOME%/conf/Catalina/localhost/%appname%.xml内的数据源连接,该应用现在正常工作。 结论:对于tomcat,看来以后要手工同步context.xml文件到%CATALINA_HOME%/conf/Catalina/localhost/%appname%.xml了。。。 文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!581.entry
问题: tb为一个table var row = tb.rows[0]; var cell=row.insertCell(row.cells.length); cell.innerHTML = "aaaa"; cell.style.display = ""; cell.style.cursor = "pointer"; cell.onmouseover = "mOver(this)"; cell.onmouseout = "mOut(this)"; cell.onclick = "mClick(this)"; 以上是在一个table中加一个单元格 但是 cell.onmouseover = "mOver(this)"; cell.onmouseout = "mOut(this)"; cell.onclick = "mClick(this)"; 却无效 也就是说在动态添加的单元格上的事件 无效 解决方法:用eval()函数 cell.setAttribute("onmouseover", eval(function(){mOver(this)})); cell.setAttribute("onmouseout", eval(function(){mOut(this)})); cell.setAttribute("onclick", eval(function(){mClick(this)})); 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!575.entry
Oracle9i之前,中文是按照二进制编码进行排序的。在oracle9i中新增了按照拼音、部首、笔画排序功能。 1、设置NLS_SORT参数值 SCHINESE_RADICAL_M 按照部首(第一顺序)、笔划(第二顺序)排序 SCHINESE_STROKE_M 按照笔划(第一顺序)、部首(第二顺序)排序 SCHINESE_PINYIN_M 按照拼音排序 2、Session级别的设置,修改ORACLE字段的默认排序方式: 按拼音:alter session set nls_sort = SCHINESE_PINYIN_M; 按笔画:alter session set nls_sort = SCHINESE_STROKE_M; 按偏旁:alter session set nls_sort = NLS_SORT=SCHINESE_RADICAL_M; 3、语句级别设置排序方式: 按照笔划排序 select * from dept order by nlssort(name,'NLS_SORT=SCHINESE_STROKE_M'); 按照部首排序 select * from dept order by nlssort(name,'NLS_SORT=SCHINESE_RADICAL_M'); 按照拼音排序,此为系统的默认排序方式 select * from dept order by nlssort(name,'NLS_SORT=SCHINESE_PINYIN_M'); 4、修改系统参数(数据库所在操作系统): set NLS_SORT=SCHINESE_RADICAL_M ;export NLS_SORT (sh) setenv NLS_SORT SCHINESE_RADICAL_M (csh) HKLC\SOFTWARE\ORACLE\home0\NLS_SORT (win注册表) Oracle 官方说明 NLS_SORT NLS_SORT specifies the collating sequence for ORDER BY queries. NLS_COMP NLS_COMP specifies the collation behavior of the database session. Property | Description | Parameter type | String | Syntax | NLS_SORT = { BINARY | linguistic_definition } | Default value | Derived from NLS_LANGUAGE | Modifiable | ALTER SESSION | Range of values | BINARY or any valid linguistic definition name | - If the value is BINARY, then the collating sequence for ORDER BY queries is based on the numeric value of characters (a binary sort that requires less system overhead).
-
If the value is a named linguistic sort, sorting is based on the order of the defined linguistic sort. Most (but not all) languages supported by the NLS_LANGUAGE parameter also support a linguistic sort with the same name. Note: Setting NLS_SORT to anything other than BINARY causes a sort to use a full table scan, regardless of the path chosen by the optimizer. BINARY is the exception because indexes are built according to a binary order of keys. Thus the optimizer can use an index to satisfy the ORDER BY clause when NLS_SORT is set to BINARY. If NLS_SORT is set to any linguistic sort, the optimizer must include a full table scan and a full sort in the execution plan. You must use the NLS_SORT operator with comparison operations if you want the linguistic sort behavior. Property | Description | Parameter type | String | Syntax | NLS_COMP = { BINARY | LINGUISTIC | ANSI } | Default value | BINARY | Modifiable | ALTER SESSION | Basic | No | Values: Normally, comparisons in the WHERE clause and in PL/SQL blocks is binary unless you specify the NLSSORT function. Comparisons for all SQL operations in the WHERE clause and in PL/SQL blocks should use the linguistic sort specified in the NLS_SORT parameter. To improve the performance, you can also define a linguistic index on the column for which you want linguistic comparisons. A setting of ANSI is for backwards compatibility; in general, you should set NLS_COMP to LINGUISTIC 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!569.entry
<bean:message key="unite.error.format" arg0="<%=Utility.getMessage("backend.livemanager.itemindex")%>"/>
最近发现tomcat 5.5.27和6.0.18都不能正确的解析上面的这段JSP代码了。说是Utility.getMessage("backend.livemanager.itemindex")这段要escape一下~~~~无语
Then I found that the JSP 2.0 standard states that quotes in JSP espressions used in tag attributes must be escaped with a backslash. This is silly, unnecessary and counter-intuitive but it is the standard...
F*cking the silly tomcat~~~~
解决的办法是在${tomcat}/conf下面的catalina.properties中加入以下内容:
org.apache.jasper.compiler. Parser.STRICT_QUOTE_ESCAPING =false
这样就关闭了这个令人讨厌的引号escape验证。
当然对于最新的jsp/servlet标准而言,上面提到的写法确实已经不合法了。所以建议以后可以用<bean:message key="unite.error.format" arg0="<%=Utility.getMessage(\”backend.livemanager.itemindex\”)%>"/>
代替。
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!562.entry
方法1:
function getAbsPoint(e)
{
var x = e.offsetLeft, y = e.offsetTop;
while(e=e.offsetParent)
{
x += e.offsetLeft;
y += e.offsetTop;
}
alert("x:"+x+","+"y:"+y);
}
方法2:
function getAbsPoint(obj)
{
var x,y;
oRect = obj.getBoundingClientRect();
x=oRect.left
y=oRect.top
alert("("+x+","+y+")")
}
JS中获得窗口属性的方法
1。获得屏幕的分辨率:
screen.width
screen.height
2。获得窗口大小:
document.body.clientWidth
document.body.clientHeight
3。获得窗口大小(包含Border、Scroll等元素)
document.body.offsetWidth
document.body.offsetHeight
为找Td坐标苦恼了很久,终于找到,方法不错,哈哈
文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!559.entry
好,说了这么多,最后让我们来看看如何在Web应用中使用Quartz。 由于Scheduler的配置相当的个性化,所以,在Web应用中,我们可以通过一个quartz.properties文件来配置QuartzServlet。不过之前让我们先来看看web.xml中如何配置 web.xml | <servlet> <servlet-name> QuartzInitializer </servlet-name> <display-name> Quartz Initializer Servlet </display-name> <servlet-class> org.quartz.ee.servlet.QuartzInitializerServlet </servlet-class> <load-on-startup> -1 </load-on-startup> <init-param> <param-name>config-file</param-name> <param-value>/quartz.properties</param-value> </init-param> <init-param> <param-name>shutdown-on-unload</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>start-scheduler-on-load</param-name> <param-value>true</param-value> </init-param> </servlet> | 这里,load-on-startup是指定QuartzServlet是否随应用启动,-1表示否,正数表示随应用启动,数值越小,则优先权越高。 初始化参数中,config-file里面可以指定QuartzServlet的配置文件,这里我们用的是quartz.properties。 shutdown-on-unload,表示是否在卸载应用时同时停止调度,该参数推荐true,否则你的tomcat进程可能停不下来。 start-scheduler-on-load,表示应用加载时就启动调度器,如果为false,则quartz.properties中指定的调度器在用户访问这个Servlet之后才会加载,在此之前,如果你通过ServletContext查找SchedulerFactory是可以找到的,但是要得到具体的Scheduler,那么你一定会发现Jvm抛出了一个NullPointerExcetion。 下面就来看看quartz.properties的真面目。 quartz.properties | org.quartz.scheduler.instanceName = PushDBScheduler org.quartz.scheduler.instanceId = one org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 4 org.quartz.threadPool.threadPriority = 4 org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin org.quartz.plugin.jobInitializer.fileName = quartz_job.xml | 我想不用多说,大家都看出来了,首先配置了基本的Scheduler实例名,并分配了ID,然后为这个调度器设定了线程池,后面是初始化插件。初始化插件是Quartz非常实用的功能,你可以用这个功能来实现Quartz的扩展性。这里配置的插件是读取job XML文件,让调度器自动载入Job。这个插件现在支持读取多个job XML文件,但是我现在还没有试过,感兴趣的读者可以自己尝试。另外就是有一个scanInterval属性,表示每隔几秒自动扫描一次job XML文件,我现在也没有试过,感兴趣的读者可以自己试验一下。注意,该参数设定为0表示不扫描。 最后,我们来看看job XML文件,这里以quartz_job.xml为例 quartz_job.xml | <quartz> <job> <job-detail> <name>ScanItemsInDB</name> <group>Scanning</group> <job-class>com.testquartz.ScanDB</job-class> <job-data-map allows-transient-data="true"> <entry> <key>testmode</key> <value>true</value> </entry> </job-data-map> </job-detail> <trigger> <cron> <name>t1</name> <group> Scanning </group> <job-name> ScanItemsInDB </job-name> <job-group> Scanning </job-group> <cron-expression>0 0/5 * * * ?</cron-expression> </cron> </trigger> </job> </quartz> | 这个文件真是非常显而易见了,我就不多说了,大家自己研究吧。 然后你只要自己写一下ScanDB这个类就可以了。 ScanDB.java | public class ScanDB implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { //你的代码 } } | 注意JobExecutionContext这个类。这个类是用来存取任务执行时的相关信息的,从中我们可以获取当前作业的Trigger、Scheduler、JobDataMap等等。 当然,Scheduler也有对应的SchedulerContext,具体的用途很像ServletContext。有兴趣的读者自己研究吧。 另外就是可以提供一个提示:在一个作业执行的时候,你就可以设定另外一个调度器,去执行另一个Job,这样你可以每个一段时间扫描一下数据库,然后看一看数据库里有没有下一个时间段待发的邮件,然后调用一个新的调度器实例,以便在指定的发送时间将其发送出去。 好了,Quartz的相关知识就总结到这里。谢谢大家。 上一篇 Quartz调度框架应用总结(续1) 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!554.entry
三.触发器 Trigger是一个抽象类,它有三个子类:SimpleTrigger,CronTrigger和NthIncludedDayTrigger。前两个比较常用。 1。SimpleTrigger:这是一个非常简单的类,我们可以定义作业的触发时间,并选择性的设定重复间隔和重复次数。 2。CronTrigger:这个触发器的功能比较强大,而且非常灵活,但是你需要掌握有关Cron表达式的知识。如果你是一个Unix系统爱好者,你很可能已经具备这种知识,但是如果你不了解Cron表达式,请看下面的Cron详解: Cron表达式由6或7个由空格分隔的时间字段组成,如表1所示: 表1 Cron表达式时间字段 位置 | 时间域名 | 允许值 | 允许的特殊字符 | 1 | 秒 | 0-59 | , - * / | 2 | 分钟 | 0-59 | , - * / | 3 | 小时 | 0-23 | , - * / | 4 | 日期 | 1-31 | , - * ? / L W C | 5 | 月份 | 1-12 | , - * / | 6 | 星期 | 1-7 | , - * ? / L C # | 7 | 年(可选) | 空值1970-2099 | , - * / | Cron表达式的时间字段除允许设置数值外,还可使用一些特殊的字符,提供列表、范围、通配符等功能,细说如下: ●星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,*在分钟字段时,表示“每分钟”; ●问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符; ●减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12; ●逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五; ●斜杠(/):x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y; ●L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值X,则表示“这个月的最后X天”,例如,6L表示该月的最后星期五; ●W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期范围; ●LW组合:在日期字段可以组合使用LW,它的意思是当月的最后一个工作日; ●井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发; ● C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。表2下面给出一些完整的Cron表示式的实例: 表2 Cron表示式示例 表示式 | 说明 | "0 0 12 * * ? " | 每天12点运行 | "0 15 10 ? * *" | 每天10:15运行 | "0 15 10 * * ?" | 每天10:15运行 | "0 15 10 * * ? *" | 每天10:15运行 | "0 15 10 * * ? 2008" | 在2008年的每天10:15运行 | "0 * 14 * * ?" | 每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59。 | "0 0/5 14 * * ?" | 每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55。 | "0 0/5 14,18 * * ?" | 每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次。 | "0 0-5 14 * * ?" | 每天14:00点到14:05,每分钟运行一次。 | "0 10,44 14 ? 3 WED" | 3月每周三的14:10分到14:44,每分钟运行一次。 | "0 15 10 ? * MON-FRI" | 每周一,二,三,四,五的10:15分运行。 | "0 15 10 15 * ?" | 每月15日10:15分运行。 | "0 15 10 L * ?" | 每月最后一天10:15分运行。 | "0 15 10 ? * 6L" | 每月最后一个星期五10:15分运行。 | "0 15 10 ? * 6L 2007-2009" | 在2007,2008,2009年每个月的最后一个星期五的10:15分运行。 | "0 15 10 ? * 6#3" | 每月第三个星期五的10:15分运行。 | 上一篇 Quartz调度框架应用总结 下一篇 Quartz调度框架应用总结(续2) 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!553.entry
前一段时间项目需要做一个定时发送消息的功能,该功能依附于Web应用上,即当Web应用启动时,该应用就开始作用。起先决定使用java.util.Timer和java.util.TimerTask来实现,但是研究了一下以后发现Java Timer的功能比较弱,而且其线程的范围不受Web应用的约束。后来发现了Quartz这个开源的调度框架,非常有趣。 首先我们要得到Quartz的最新发布版。目前其最新的版本是1.6。我们可以从以下地址获得它的完整下载包,包中可谓汤料十足,不仅有我们要的quartz.jar,更包含多个例程和详细的文档,从API到配置文件的XSD一应俱全。感兴趣的朋友也可以在src目录下找到该项目的源码一看究竟。 废话少说,下面就来看一看这个东东是怎么在Java Web Application中得以使用的。 首先不得不提出的是Quartz的三个核心概念:调度器、触发器、作业。让我们来看看他们是如何工作的吧。 一.作业总指挥——调度器 1. Scheduler接口 该接口或许是整个Quartz中最最上层的东西了,它提携了所有触发器和作业,使它们协调工作。每个Scheduler都存有JobDetail和Trigger的注册,一个Scheduler中可以注册多个JobDetail和多个Trigger,这些JobDetail和Trigger都可以通过group name和他们自身的name加以区分,以保持这些JobDetail和Trigger的实例在同一个Scheduler内不会冲突。所以,每个Scheduler中的JobDetail的组名是唯一的,本身的名字也是唯一的(就好像是一个JobDetail的ID)。Trigger也是如此。 Scheduler实例由SchedulerFactory产生,一旦Scheduler实例生成后,我们就可以通过生成它的工厂来找到该实例,获取它相关的属性。下面的代码为我们展示了如何从一个Servlet中找到SchedulerFactory并获得相应的Scheduler实例,通过该实例,我们可以获取当前作业中的testmode属性,来判断该作业是否工作于测试模式。 //从当前Servlet上下文中查找StdSchedulerFactory ServletContext ctx=request.getSession().getServletContext(); StdSchedulerFactory factory = (StdSchedulerFactory) ctx.getAttribute("org.quartz.impl.StdSchedulerFactory.KEY"); Scheduler sch = null; try { //获取调度器 sch = factory.getScheduler("SchedulerName"); //通过调度器实例获得JobDetail,注意领会JobDetailName和GroupName的用法 JobDetail jd=sch.getJobDetail("JobDetailName", "GroupName"); Map jobmap1=jd.getJobDataMap(); istest=jobmap1.get("testmode")+""; } catch (Exception se) { //如果得不到当前作业,则从配置文件中读取testmode ReadXML("job.xml").get(“job.testmode”); } | Scheduler实例生成后,它处于"stand-by"模式,需要调用其start方法来使之投入运作。 public class SendMailShedule{ //设置标准SchedulerFactory static SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); static Scheduler sched; public static void run()throws Exception{ //生成Scheduler实例 sched = schedFact.getScheduler(); //创建一个JobDetail实例,对应的Job实现类是SendMailJob JobDetail jobDetail = new JobDetail("myJob",sched.DEFAULT_GROUP,SendMailJob.class); //设置CronTrigger,利用Cron表达式设定触发时间 CronTrigger trigger = new CronTrigger("myTrigger","test","0 0 8 1 * ?"); sched.scheduleJob(jobDetail, trigger); sched.start(); } public static void stop()throws Exception{ sched.shutdown(); } } | 另外,我们也可以通过监听器来跟踪作业和触发器的工作状态。 二.作业及其相关 1. Job 作业实际上是一个接口,任何一个作业都可以写成一个实现该接口的类,并实现其中的execute()方法,来完成具体的作业任务。 2. JobDetail JobDetail可以指定我们作业的详细信息,比如可以通过反射机制动态的加载某个作业的实例,可以指定某个作业在单个调度器内的作业组名称和具体的作业名称,可以指定具体的触发器。 一个作业实例可以对应多个触发器(也就是说学校每天10点放一次眼保健操录音,下午3点半可以再放一次),但是一个触发器只能对应一个作业实例(10点钟的时候学校不可能同时播放眼保健操和广播体操的录音)。 3. JobDataMap 这是一个给作业提供数据支持的数据结构,使用方法和java.util.Map一样,非常方便。当一个作业被分配给调度器时,JobDataMap实例就随之生成。 Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。 正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。 如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。 JobDataMap实例也可以与一个触发器相关联。这种情况下,对于同一作业的不同触发器,我们可以在JobDataMap中添加不同的数据,以便作业在不同时间执行时能够提供更为灵活的数据支持(学校上午放眼保健操录音第一版,下午放第二版)。 不管是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。 下一篇 Quartz调度框架应用总结(续1) 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!550.entry
1 框架编程概述 一个Html 页面可以有一个或多个子框架,这些子框架以<iframe>来标记,用来显示一 个独立的Html 页面。这里所讲的框架编程包括框架的自我控制以及框架之间的互相访问, 例如从一个框架中引用另一个框架中的JavaScript变量、调用其他框架内的函数、控制另一 个框架中表单的行为等。 2 框架间的互相引用 一个页面中的所有框架以集合的形式作为window 对象的属性提供,例如: window.frames 就表示该页面内所有框架的集合,这和表单对象、链接对象、图片对象等是 类似的,不同的是,这些集合是document 的属性。因此,要引用一个子框架,可以使用如 下语法: window.frames[“frameName”]; window.frames.frameName window.frames[index] 其中,window字样也可以用self代替或省略,假设frameName 为页面中第一个框架, 则以下的写法是等价的: self.frames[“frameName”] self.frames[0] frames[0] frameName 了解了如何引用一个框架,那么引用的这个框架到底是什么呢?其实,每个框架都对应 一个HTML 页面,所以这个框架也是一个独立的浏览器窗口,它具有窗口的所有性质,所 谓对框架的引用也就是对window 对象的引用。有了这个window 对象,就可以很方便地对 其中的页面进行操作,例如使用window.document对象向页面写入数据、使用window.location 属性来改变框架内的页面等。 下面分别介绍不同层次框架间的互相引用: 2.1.父框架到子框架的引用 知道了上述原理,从父框架引用子框架变的非常容易,即: window.frames[“frameName”]; 这样就引用了页面内名为frameName 的子框架。如果要引用子框架内的子框架,根据 引用的框架实际就是window对象的性质,可以这样实现: window.frames[“frameName”].frames[“frameName2”]; 这样就很引用到了二级子框架,以此类推,可以实现多层框架的引用。 2.2.子框架到父框架的引用 每个window对象都有一个parent属性,表示它的父框架。如果该框架已经是顶层框架, 则window.parent 还表示该框架本身。 2.3.兄弟框架间的引用 如果两个框架同为一个框架的子框架,它们称为兄弟框架,可以通过父框架来实现互相 引用,例如一个页面包括2 个子框架: <frameset rows="50%,50%"> <frame src="1.html" name="frame1" /> <frame src="2.html" name="frame2" /> </frameset> 在frame1 中可以使用如下语句来引用frame2: self.parent.frames[“frame2”]; 2.4.不同层次框架间的互相引用 框架的层次是针对顶层框架而言的。当层次不同时,只要知道自己所在的层次以及另一 个框架所在的层次和名字,利用框架引用的window对象性质,可以很容易地实现互相访问, 例如: self.parent.frames[“childName”].frames[“targetFrameName”]; 2.5.对顶层框架的引用 和parent 属性类似,window 对象还有一个top 属性。它表示对顶层框架的引用,这可 以用来判断一个框架自身是否为顶层框架,例如: //判断本框架是否为顶层框架 if(self==top){ //dosomething } 3 改变框架的载入页面 前面已经讲到,对框架的引用就是对window对象的引用,利用window对象的location 属性,可以改变框架的导航,例如: window.frames[0].location=”1.html”; 这就将页面中第一个框架的页面重定向到1.html,利用这个性质,甚至可以使用一条链 接来更新多个框架。 <frameset rows="50%,50%"> <frame src="1.html" name="frame1" /> <frame src="2.html" name="frame2" /> </frameset> <!--somecode--> <a href=”frame1.location=’3.html;frame2.location=’4.html’” onclick=””>link</a> <!--somecode--> 4 引用其他框架内的JavaScript变量和函数 在介绍引用其他框架内JavaScript变量和函数的技术之前,先来看以下代码: <script language="JavaScript" type="text/javascript"> <!-- function hello(){ alert("hello,ajax!"); } window.hello(); //--> </script> 如果运行了这段代码,会弹出“hello,ajax!”的窗口,这正是执行hello()函数的结果。 那为什么hello()变成了window对象的方法呢?事实上,在一个页面内定义的所有全局变量 和全局函数都是作为window对象的成员。例如: var a=1; alert(window.a); 就会弹出对话框显示为1。同样的原理,在不同框架之间共享变量和函数,就是要通过 window对象来调用。 为了方便大家的理解,下面举例说明。一个商品浏览页面由两个子框架组成,左侧表示 商品分类的链接;当用户单击分类链接时,右侧显示相应的商品列表;用户可以单击商品旁 的【购买】链接将商品加入购物车。 在这个例子中,可以利用左侧导航页面来存储用户希望购买的商品,因为当用户单击导 航链接时,变化的是另外一个页面,即商品展示页面,而导航页面本身是不变的,因此其中 的JavaScript变量不会丢失,可以用来存储全局数据。其实现原理如下: 假设左侧页面为links.html,右侧页面为show.html,页面结构如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title> New Document </title> </head> <frameset cols="20%,80%"> <frame src="link.html" name="link" /> <frame src="show.html" name="show" /> </frameset> </html> 在show.html 中展示的商品旁边可以加入这样一条语句: <a href=”void(0)” onclick=”self.parent.link.addToOrders(32068)”>加入购物车</a> 其中link表示导航框架,在link.html 页面中定义了arrOrders数组来存储商品的id,函 数addToOrders()用来响应商品旁边【购买】链接的单击事件,它接收的参数id 表示商品的 id,例子中是一个id为32068 的商品: <script language="JavaScript" type="text/javascript"> <!-- var arrOrders=new Array(); function addToOrders(id){ arrOrders.push(id); } //--> </script> 这样,在结帐页面或是购物车浏览页面就可以用arrOrders来获取所有准备购买的商品。 框架可以使一个页面划分为功能独立的多个模块,每个模块之间彼此独立,但又可以通 过window 对象的引用来建立联系,是web 开发中的一个重要机制。在Ajax 开发中,还可 以利用隐藏框架实现各种技巧,在后面介绍Ajax 实例编程时可以发现,无刷新上传文件以 及解决Ajax的前进后退问题都会用到这种技术。 以上内容来自《征服AJAX》 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!423.entry
[以下是转文] Ajax从数据库里读取数据不能及时更新,这是因为浏览器的缓存机制。本文提出了四种解决办法。 以下是引用片段: 在电信做的小灵通短信订餐系统中,有几个页面要用到三级联动下拉框,为了使用户体验更好,使服务器缓解一定压力,我决定使用AJAX来完成这个功能,可是我却粗心大意,漏掉了一个环节,使得ajax从数据库里读取数据不能及时更新.这是因为浏览器的缓存机制. 有4种方法可以解决这个问题: 1.在请求的URL后面加一个时间参数,如: time=new date() 当然也可以添加其他性质参数,只要是随机参数就可以, open("GET",url+"?t="+Math.random(),false) 或者 url+"?timeStamp="+new Date().getTime(); 2. js 代码 function ajaxRead(file){ var xmlObj = null; if(window.XMLHttpRequest){ xmlObj = new XMLHttpRequest(); } else if(window.ActiveXObject){ xmlObj = new ActiveXObject("Microsoft.XMLHTTP"); } else { return; } xmlObj.onreadystatechange = function(){ if(xmlObj.readyState == 4){ processXML(xmlObj.responseXML); } else{ document.getElementById ('playernews').innerHTML='采用AJAX来实现数据的读取,正在加载...'; } } xmlObj.open ('GET', file, true); xmlObj.send (''); } function show() { ajaxRead('*.jsp'); setInterval("ajaxRead('new.php')",30000); //自动更新 } 3.加上 xmlhttp.setRequestHeader("Cache-Control","no-cache"); 4.在XmlHttpRequest发送请求之前加上 XmlHttpRequest.setRequestHeader("If-Modified-Since","0"), 如:在 XXXXX.send(YYYYYY). [以下是本人注释] 在使用prototype.js的时候,如果想使用方法3和4,需要这样写: var myAjax = new Ajax.Request( request_url, { method:'get', requestHeaders: ['Cache-Control','no-cache','If-Modified-Since','0'], parameters:request_pars, asynchronous:true, //true---异步;false---同步.默认为true onComplete:processRequest } ); 注意红色的代码部分,你可能注意到这是把3、4两种方法结合起来使用的。。。 好了,如果你是使用json的话,这个方法一样好用。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!421.entry
在www.json.org上公布了很多Java下的json解析工具,其中org.json和json-lib比较简单,两者使用上差不多。下面两段源代码是分别使用这两个工具解析和构造JSON的演示程序。 http://nchc.dl.sourceforge.net/sourceforge/json-lib/json-lib-2.2.1-jdk15.jar (Needs libs below:) [ - jakarta commons-lang 2.3
- jakarta commons-beanutils 1.7.0
- jakarta commons-collections 3.2
- jakarta commons-logging 1.1
- ezmorph 1.0.4
] 这是使用json-lib的程序: import java.util.HashMap; import java.util.Map; import net.sf.json.JSONObject; public class Test { public static void main(String[] args) { String json = "{\"name\":\"reiz\"}"; JSONObject jsonObj = JSONObject.fromObject(json); String name = jsonObj.getString("name"); jsonObj.put("initial", name.substring(0, 1).toUpperCase()); String[] likes = new String[] { "JavaScript", "Skiing", "Apple Pie" }; jsonObj.put("likes", likes); Map <String, String> ingredients = new HashMap <String, String>(); ingredients.put("apples", "3kg"); ingredients.put("sugar", "1kg"); ingredients.put("pastry", "2.4kg"); ingredients.put("bestEaten", "outdoors"); jsonObj.put("ingredients",ingredients); System.out.println(jsonObj); } } http://www.json.org/java/json.zip 这是使用org.json的程序: import java.util.HashMap; import java.util.Map; import org.json.JSONException; import org.json.JSONObject; public class Test { public static void main(String[] args) throws JSONException { String json = "{\"name\":\"reiz\"}"; JSONObject jsonObj = new JSONObject(json); String name = jsonObj.getString("name"); jsonObj.put("initial", name.substring(0, 1).toUpperCase()); String[] likes = new String[] { "JavaScript", "Skiing", "Apple Pie" }; jsonObj.put("likes", likes); Map <String, String> ingredients = new HashMap <String, String>(); ingredients.put("apples", "3kg"); ingredients.put("sugar", "1kg"); ingredients.put("pastry", "2.4kg"); ingredients.put("bestEaten", "outdoors"); jsonObj.put("ingredients", ingredients); System.out.println(jsonObj); System.out.println(jsonObj); } } 两者的使用几乎是相同的,但org.json比json-lib要轻量得多,前者没有任何依赖,而后者要依赖ezmorph和commons的lang、logging、beanutils、collections等组件。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!419.entry
JSON (JavaScript Object Notation)一种简单的数据格式,比xml更轻巧。 JSON 是 JavaScript 原生格式,这意味着在 JavaScript 中处理 JSON 数据不需要任何特殊的 API 或工具包。 JSON的规则很简单: 对象是一个无序的“‘名称/值’对”集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个“名称”后跟一个“:”(冒号);“‘名称/值’ 对”之间使用“,”(逗号)分隔。具体细节参考http://www.json.org/json-zh.html 举个简单的例子: js 代码 - function showJSON() {
- var user =
- {
- "username":"andy",
- "age":20,
- "info": { "tel": "123456", "cellphone": "98765"},
- "address":
- [
- {"city":"beijing","postcode":"222333"},
- {"city":"newyork","postcode":"555666"}
- ]
- }
-
- alert(user.username);
- alert(user.age);
- alert(user.info.cellphone);
- alert(user.address[0].city);
- alert(user.address[0].postcode);
- }
这表示一个user对象,拥有username, age, info, address 等属性。 同样也可以用JSON来简单的修改数据,修改上面的例子 js 代码 - function showJSON() {
- var user =
- {
- "username":"andy",
- "age":20,
- "info": { "tel": "123456", "cellphone": "98765"},
- "address":
- [
- {"city":"beijing","postcode":"222333"},
- {"city":"newyork","postcode":"555666"}
- ]
- }
-
- alert(user.username);
- alert(user.age);
- alert(user.info.cellphone);
- alert(user.address[0].city);
- alert(user.address[0].postcode);
-
- user.username = "Tom";
- alert(user.username);
- }
JSON提供了json.js包,下载http://www.json.org/json.js 后,将其引入然后就可以简单的使用object.toJSONString()转换成JSON数据。 js 代码 - function showCar() {
- var carr = new Car("Dodge", "Coronet R/T", 1968, "yellow");
- alert(carr.toJSONString());
- }
-
- function Car(make, model, year, color) {
- this.make = make;
- this.model = model;
- this.year = year;
- this.color = color;
- }
可以使用eval来转换JSON字符到Object js 代码 - function myEval() {
- var str = '{ "name": "Violet", "occupation": "character" }';
- var obj = eval('(' + str + ')');
- alert(obj.toJSONString());
- }
或者使用parseJSON()方法 js 代码 - function myEval() {
- var str = '{ "name": "Violet", "occupation": "character" }';
- var obj = str.parseJSON();
- alert(obj.toJSONString());
- }
下面使用prototype写一个JSON的ajax例子。 先写一个servlet (我的是servlet.ajax.JSONTest1.java)就写一句话 java 代码 - response.getWriter().print("{ \"name\": \"Violet\", \"occupation\": \"character\" }");
再在页面中写一个ajax的请求 js 代码 - function sendRequest() {
- var url = "/MyWebApp/JSONTest1";
- var mailAjax = new Ajax.Request(
- url,
- {
- method: 'get',
- onComplete: jsonResponse
- }
- );
- }
-
- function jsonResponse(originalRequest) {
- alert(originalRequest.responseText);
- var myobj = originalRequest.responseText.parseJSON();
- alert(myobj.name);
- }
prototype-1.5.1.js中提供了JSON的方法,String.evalJSON(), 可以不使用json.js, 修改上面的方法 js 代码 - function jsonResponse(originalRequest) {
- alert(originalRequest.responseText);
- var myobj = originalRequest.responseText.evalJSON(true);
- alert(myobj.name);
- }
JSON还提供了java的jar包 http://www.json.org/java/index.html API也很简单,下面举个例子 在javascript中填加请求参数 js 代码 - function sendRequest() {
- var carr = new Car("Dodge", "Coronet R/T", 1968, "yellow");
- var pars = "car=" + carr.toJSONString();
-
- var url = "/MyWebApp/JSONTest1";
- var mailAjax = new Ajax.Request(
- url,
- {
- method: 'get',
- parameters: pars,
- onComplete: jsonResponse
- }
- );
- }
使用JSON请求字符串就可以简单的生成JSONObject并进行解析,修改servlet添加JSON的处理(要使用json.jar) java 代码 - private void doService(HttpServletRequest request, HttpServletResponse response) throws IOException {
- String s3 = request.getParameter("car");
- try {
- JSONObject jsonObj = new JSONObject(s3);
- System.out.println(jsonObj.getString("model"));
- System.out.println(jsonObj.getInt("year"));
- } catch (JSONException e) {
- e.printStackTrace();
- }
- response.getWriter().print("{ \"name\": \"Violet\", \"occupation\": \"character\" }");
- }
同样可以使用JSONObject生成JSON字符串,修改servlet java 代码 - private void doService(HttpServletRequest request, HttpServletResponse response) throws IOException {
- String s3 = request.getParameter("car");
- try {
- JSONObject jsonObj = new JSONObject(s3);
- System.out.println(jsonObj.getString("model"));
- System.out.println(jsonObj.getInt("year"));
- } catch (JSONException e) {
- e.printStackTrace();
- }
-
- JSONObject resultJSON = new JSONObject();
- try {
- resultJSON.append("name", "Violet")
- .append("occupation", "developer")
- .append("age", new Integer(22));
- System.out.println(resultJSON.toString());
- } catch (JSONException e) {
- e.printStackTrace();
- }
- response.getWriter().print(resultJSON.toString());
- }
js 代码 - function jsonResponse(originalRequest) {
- alert(originalRequest.responseText);
- var myobj = originalRequest.responseText.evalJSON(true);
- alert(myobj.name);
- alert(myobj.age);
- }
参考 http://www.json.org/js.html http://www.blogjava.net/Jkallen/archive/2006/03/28/37905.html http://www.json.org/ http://www.prototypejs.org/learn/json http://www.json.org/java/index.html http://www.ibm.com/developerworks/cn/web/wa-ajaxintro10/index.html 使用JSON JSON也就是JavaScript Object Notation,是一个描述数据的轻量级语法。JSON的优雅是因为它是JavaScript语言的一个子集。接下来你将看到它为什么如此重要。首先,来比较一下JSON和XML语法。 JSON和XML都使用结构化方法描述数据。例如一个地址簿应用程序可以提供用来产生XML格式的地址卡的web服务: <?xml version='1.0' encoding='UTF-8'?> <card> <fullname>Sean Kelly</fullname> <org>SK Consulting</org> <emailaddrs> <address type='work'>kelly@seankelly.biz</address> <address type='home' pref='1'>kelly@seankelly.tv</address> </emailaddrs> <telephones> <tel type='work' pref='1'>+1 214 555 1212</tel> <tel type='fax'>+1 214 555 1213</tel> <tel type='mobile'>+1 214 555 1214</tel> </telephones> <addresses> <address type='work' format='us'>1234 Main St Springfield, TX 78080-1216</address> <address type='home' format='us'>5678 Main St Springfield, TX 78080-1316</address> </addresses> <urls> <address type='work'>http://seankelly.biz/</address> <address type='home'>http://seankelly.tv/</address> </urls> </card> 使用JSON, 形式如下: { "fullname": "Sean Kelly", "org": "SK Consulting", "emailaddrs": [ {"type": "work", "value": "kelly@seankelly.biz"}, {"type": "home", "pref": 1, "value": "kelly@seankelly.tv"} ], "telephones": [ {"type": "work", "pref": 1, "value": "+1 214 555 1212"}, {"type": "fax", "value": "+1 214 555 1213"}, {"type": "mobile", "value": "+1 214 555 1214"} ], "addresses": [ {"type": "work", "format": "us", "value": "1234 Main StnSpringfield, TX 78080-1216"}, {"type": "home", "format": "us", "value": "5678 Main StnSpringfield, TX 78080-1316"} ], "urls": [ {"type": "work", "value": "http://seankelly.biz/"}, {"type": "home", "value": "http://seankelly.tv/"} ] } 如你所看到的,JSON有结构化的嵌套数据元素,这一点和XML相似。JSON也是基于文本的,XML也是如此。两者都使用Unicode。JSON和XML都很容易阅读。主观上,JSON更清晰,冗余更少。JSON WEB站点严格地描述了JSON语法,目前就是这样的。它确实是一个简单的小语言! XML确实适合标记文档,但是JSON是数据交互的理想格式。每个JSON文档描述了一个这样一个对象,该对象包含有:嵌套对象、数组、字符串、数字、布尔值或空值。 在这些地址卡例子代码中,JSON版本是更轻量级的,只占用了682字节的空间,而XML版本需要744字节空间。尽管这不是一个可观的节省。而实际的好处则来自解析过程。 XML对比JSON:地位丧失 通过使用XMLHttpRequest对象,可以从你的基于AJAX的应用程序取得XML和JSON文件。典型的,交互代码如下: var req = new XMLHttpRequest(); req.open("GET", "http://localhost/addr?cardID=32", /*async*/true); req.onreadystatechange = myHandler; req.send(/*no params*/null); 作为WEB服务器响应,你提供的处理器函数(myHandler函数)被多次调用,为你提供提前终止事务,更新进度条等机会。通常的,只有在web请求完成以后才起作用:那时,你就可以使用返回的数据了。 为了处理XML版本的地址卡数据,myHandler的代码如下: function myHandler() { if (req.readyState == 4 /*complete*/) { // Update address field in a form with first street address var addrField = document.getElementById('addr'); var root = req.responseXML; var addrsElem = root.getElementsByTagName('addresses')[0]; var firstAddr = addrsElem.getElementsByTagName('address')[0]; var addrText = fistAddr.firstChild; var addrValue = addrText.nodeValue; addrField.value = addrValue; } } 值得注意的是你不必解析XML文档:XMLHttpRequest对象自动地解析了,并使responseXML中的DOM树可用。通过使用responseXML属性,可以调用getElementsByTagName方法查找文档的地址部分,你还可以使用第一个去找到它。然后,可以再次调用getElementsByTagName在地址部分查找第一个地址元素。这就取得了文档的第一个DOM子节点,就是一个文本节点,并取得节点的值,这就是你想要的街道地址。最后,可以在表单域中显示结果。 确实不是一个简单的工作,现在,使用JSON再试一下: function myHandler() { if (req.readyState == 4 /*complete*/) { var addrField = document.getElementById('addr'); var card = eval('(' + req.responseText + ')'); addrField.value = card.addresses[0].value; } } 你所做的第一件事情就是解析JSON响应。但是,因为JSON是JavaScript的一个子集,你可以使用JavaScript自己的编译器来解析它,通过调用eval函数。解析JSON仅需要一行!此外,操纵JSON中的对象就像操纵其他JavaScript对象一样。这显然要比通过DOM树来操纵简单,例如: card.addresses[0].value 是第一个街道地址, "1234 Main Stb &" card.addresses[0].type 是地址类型, "work" card.addresses[1] 是家庭地址对象 card.fullname 是card的名称, "Sean Kelly" 如果更仔细观察,你可能会发现XML格式中文档至少有一个跟元素,card。这在JSON里是不存在的,为什么? 大概就是,如果你正在开发JavaScript来访问Web服务,你已经知道你想要得到的。然而,你可以在JSON中这么使用: {"card": {"fullname": ...}} 使用这个技术,你的JSON文件总是以一个带有单一命名属性的对象开始,该属性标识了对象的种类。 JSON是快速可靠的吗? JSON提供轻量的小文档,并且JSON在JavaScript更容易使用。XMLHttpRequest自动为你解析了XML文档,而你还要手工解析JSON文件,但是解析JSON比解析XML更慢么?作者通过几千次的反复测试,使用XMLHttpRequest解析XML和解析JSON,结果是解析JSON比XML要快10倍!当把AJAX当作桌面应用看待时,速度是最重要的因素,很明显,JSON更优秀。 当然,你不能总是控制服务器端来为AJAX程序产生数据。你还可以使用第三方服务器代替服务器提供XML格式的输出。并且,如果服务器恰好提供JSON,你可以确定你真的想使用它吗? 代码中值得注意的是,你将响应文本直接传入到eval中。如果你控制着服务器,就可以这么做。如果不是,一个恶意服务器可以使你的浏览器执行危险操作。在这样的情况下,你最好使用写在JavaScript中的代码来解析JSON。幸运地,这已经有了。 说到解析,Python爱好者可能注意到JSON不只是JavaScript的子集,它还是Python的一个子集。你可以在Python中直接执行JSON,或者使用安全JSON解析代替。JSON.org网站列举了许多常用JSON解析器。 服务器端的JSON 到现在为止,你或许将焦点注意在运行在客户浏览器中的基于AJAX的web应用程序使用JSON。自然地,首先,JSON格式的数据必须在服务器端产生。幸运地是,创建JSON或将其他存在的数据转换成JSON是相当简单的。一些WEB应用程序框架,例如TurboGears,自动包括对JSON输出的支持。 此外商业WEB服务提供商也注意到了JSON。Yahoo最近创建了许多基于JSON的web服务。Yahoo的多种搜索服务,履行计划,del.icio.us,还有高速公路交通服务也都支持JSON输出。毫无疑问,其他主要WEB服务提供商也将加入到对JSON的支持中。 总结 JSON的聪明在于它是JavaScript和Python的子集,使得它更易用,为AJAX提供高效的数据交互。它解析更快,比XML更易使用。JSON正成为现在“Web 2.0”的最强音。每个开发者,无论是标准桌面应用程序或Web应用程序,越来越注意到了它的简单和便捷。我希望你能体会到在buzzword-compliant, Web-2.0-based, AJAX-enabled, 敏捷开发中应用到JSON的乐趣。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!418.entry
感谢原作者的努力,struts1.x用的熟练的朋友可以很快上手Struts2 下面只粘贴六篇的链接地址。 第一篇|第二篇|第三篇|第四篇|第五篇|第六篇 另外,听说JDK1.6要出Update10了,通常这种Update Release是不会对核心的公共API做出修改的。这次Update虽然也没有修改公共API,但是新增了许多细节,并且更改了JRE的分发方式,支持按需获取不同大小的JRE,从而使得Java程序更轻快的运行在各种系统中。 有关此次更新的官网链接: Introducing Java SE 6 update 10 Beta 看来JavaFX有望成为Java技术带来的下一个经济增长点。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!415.entry
Mozilla Public License MPL License,允许免费重发布、免费修改,但要求修改后的代码版权归软件的发起者。这种授权维护了商业软件的利益,,它要求基于这种软件得修改无偿贡献版权给该软件。这样,围绕该软件得所有代码得版权都集中在发起开发人得手中。但MPL是允许修改,无偿使用得。MPL软件对链接没有要求。 BSD开源协议 BSD开源协议是一个给于使用者很大自由的协议。可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。 当你发布使用了BSD协议的代码,或则以BSD协议代码为基础做二次开发自己的产品时,需要满足三个条件: 1. 如果再发布的产品中包含源代码,则在源代码中必须带有原来代码中的BSD协议。 2. 如果再发布的只是二进制类库/软件,则需要在类库/软件的文档和版权声明中包含原来代码中的BSD协议。 3. 不可以用开源代码的作者/机构名字和原来产品的名字做市场推广。 BSD代码鼓励代码共享,但需要尊重代码作者的著作权。BSD由于允许使用者修改和重新发布代码,也允许使用或在BSD代码上开发商业软件发布和销售,因此是对商业集成很友好的协议。而很多的公司企业在选用开源产品的时候都首选BSD协议,因为可以完全控制这些第三方的代码,在必要的时候可以修改或者二次开发。 Apache Licence 2.0 Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。需要满足的条件: 1. 需要给代码的用户一份Apache Licence 2. 如果你修改了代码,需要再被修改的文件中说明。 3. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标,专利声明和其他原来作者规定需要包含的说明。 4. 如果再发布的产品中包含一个Notice文件,则在Notice文件中需要带有Apache Licence。你可以在Notice中增加自己的许可,但不可以表现为对Apache Licence构成更改。 Apache Licence也是对商业应用友好的许可。使用者也可以在需要的时候修改代码来满足需要并作为开源或商业产品发布/销售。 GPL GPL许可证是自由软件的应用最广泛的软件许可证,人们可以修改程式的一个或几个副本或程式的任何部分,以此形成基於这些程式的衍生作品。必须在修改过的档案中附有明显的说明:您修改了此一档案及任何修改的日期。您必须让您发布或出版的作品,包括本程式的全部或一部分,或内含本程式的全部或部分所衍生的作品,允许第三方在此许可证条款下使用,并且不得因为此项授权行为而收费。 LGPL Linux就是采用了GPL。GPL协议和BSD, Apache Licence等鼓励代码重用的许可很不一样。GPL的出发点是代码的开源/免费使用和引用/修改/衍生代码的开源/免费使用,但不允许修改后和衍生的代码做为闭源的商业软件发布和销售。这也就是为什么我们能用免费的各种linux,包括商业公司的linux和linux上各种各样的由个人,组织,以及商业软件公司开发的免费软件了。 GPL协议的主要内容是只要在一个软件中使用(“使用”指类库引用,修改后的代码或者衍生代码)GPL协议的产品,则该软件产品必须也采用 GPL协议,既必须也是开源和免费。这就是所谓的”传染性”。GPL协议的产品作为一个单独的产品使用没有任何问题,还可以享受免费的优势。 由于GPL严格要求使用了GPL类库的软件产品必须使用GPL协议,对于使用GPL协议的开源代码,商业软件或者对代码有保密要求的部门就不适合集成/采用作为类库和二次开发的基础。 其它细节如再发布的时候需要伴随GPL协议等和BSD/Apache等类似 Public Domain 公共域授权。将软件授权为公共域,这些软件包没有授权协议,任何人都可以随意使用它。 Artistic许可 使作者保持对进一步开发的控制。
文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!414.entry
前一段时间一直被正则表达式搞得晕头转向,现在好了,微软仅仅用几行就把正则表达式说完了。。。 虽然不是很全面,但是足够用了。 正则表达式运算符 “表达式”属性支持以下正则表达式运算符: 表达式 | 说明 | . | 指示任何字符。 | \ | 指示后面的字符应按原义而不是作为特殊字符进行解释。例如,\. 指示“.”。 | () | 将括号内的运算符分组。 | {n} | 生成前面项的 n 个实例。例如,a{2} 生成“aa”。 | {n,m} | 生成前面项的至少 n 个实例但不超过 m 个实例。例如,a{2,4} 生成“aa”、“aaa”或“aaaa”。 | {n,} | 生成前面项的 n 个或更多实例。例如,a{2,} 生成“aa”、“aaa”、“aaaa”、“aaaaa”等。 | * | 生成前面项的 0 个或多个实例。 | + | 生成前面项的 1 个或多个实例。 | ? | 生成前面项的 0 个或 1 个实例。 | | | 在 | 字符任一侧生成项。 | [aeiou] | 生成括号内的任何字符。 | [a-z] | 生成字符指定范围内的任何字符。 | [^aeiou] | 生成除括号内字符以外的任何字符。 | 原文地址:http://msdn2.microsoft.com/zh-cn/library/aa833197(VS.80).aspx 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!409.entry
更改IE浏览器默认的源文件编辑器 用户在浏览网页时,如果在网页中单击鼠标右键并选择菜单中的“查看源文件”选项后,系统就会调用记事本打开该网页的HTML源文件,用户可以通过修改注册表来更改默认的打开程序,然后如下: 一:打开注册表。 “开始”菜单->“运行”->输入 regedit 然后点确定。 二:打开HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\INTERNET EXPLORER,在其下新建一个主键"View Source Editor",在其下再新一个主键"Editor Name",又击右侧窗口中的"默认"将数值设为更换程序的路径及文件名. 三:重新启动INTERNET EXPLORER,查看某个网页的源文件,用户就会发现打开的程序已经更改了. **************************************************对于有需要查看网页源文件的朋友来说,用系统自带的文本编辑器,远远达不到我们的要求。 用UltraEdit就非常好了,它是一个非常出名,而且好用的文本编辑器。功能非常的多。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!406.entry
前段时间写的 深入浅出Java中文问题系列描述了很多Java各种应用中出现的中文问题,唯独没有说到文件的读写。最近用Java处理文件的时候,同样遇到了中文问题,觉得还是有必要总结一下,也使该系列的文章更加完整。 熟悉Java 的人都知道,在Java中,IO是分成两大部分的,分别对应字节和字符的操作,也就是Stream和Character,它们之间可以相互转换,桥梁就是StreamInputReader/StreamOutputWriter。为了更加清楚的了解它们之间的关系,我们可以看看它们所在的类结构。 java.lang.Object - java.io.InputStream (implements java.io.Closeable)
- java.io.OutputStream (implements java.io.Closeable, java.io.Flushable)
- java.io.RandomAccessFile (implements java.io.Closeable, java.io.DataInput, java.io.DataOutput)
- java.io.Reader (implements java.io.Closeable, java.lang.Readable)
- java.io.BufferedReader
- java.io.InputStreamReader
- java.io.Writer (implements java.lang.Appendable, java.io.Closeable, java.io.Flushable)
- java.io.BufferedWriter
- java.io.OutputStreamWriter
上面列出来的并不是Java.io中全部的类,但是对于文件读写来说已经足够了。通常,我们使用以下代码来进行文件的读写: public void naiveWrite() throws IOException{ FileWriter fw = new FileWriter("test.txt"); fw.write("中文你好"); fw.close(); }
public String naiveRead() throws IOException{ FileReader fr = new FileReader("test.txt"); BufferedReader br = new BufferedReader(fr); String str = br.readLine(); br.close(); fr.close(); return str; } 如果我们的是中文平台,上面代码是可以正常运行的。但是如果我们把这些代码放到一个ISO8859-1的系统上,中文问题就出来了(当然,前提你在javac的时指定了编码方式,如javac -encoding gb2312 ***.java,参看该系列前面的文章)。为什么呢?这是因为FileWriter和FileReader是辅助类,为了方便大家使用 OutputSteamWriterer 和 InputStreamReader 而屏蔽了字符集的设定操作,而采用系统默认的编码方式,而这在很多情况下也能满足用户的需求。在中文系统中,系统的默认编码方式一般是GBK,因此文件中中文的读写是没有问题的。但是,当程序运行在ISO8859-1的系统中时,JVM使用ISO8859-1对中文进行编码,当然就认不到了,于是那一个个的问号就来了。 那怎么办呢?既然捷径走不通,我们就只好使用OutputSteamWriter 和 InputStreamReader了。 public void write() throws IOException{ OutputStreamWriter osw = new OutputStreamWriter( new FileOutputStream("test.txt"), "utf-8"); osw.write("中国万岁"); osw.close(); } public String read() throws IOException{ InputStreamReader isr = new InputStreamReader( new FileInputStream("test.txt"),"utf-8"); BufferedReader br = new BufferedReader(isr); String str = br.readLine(); br.close(); isr.close(); return str; } 在这里,我们指定文件读写的编码方式为utf-8,当然对于中文来说GBK和GB2312也是可以的,但是推荐使用UTF-8,这样对于软件的国际化很有好处。其实,这里指定编码方式进行文件的写入跟我们使用记事本等编辑器的另存为,并且指定格式为“UTF-8”在本质上是一样的。通过上述处理后,程序就可以跨平台运行了。 在处理文件的过程中,我们还会用到RandomAccessFile这个类来随机访问文件。这里,如果我们写入字符串的时候调用writeChars,那么,如果写入的是中文,中文问题就又会出现了。因为此时RandomAccessFile并没有使用系统的默认编码来写入文件,而是直接将内存中的二进制数据直接写到文件中去。如何解决这个问题呢?只要读写对称就行了。 public void randWrite() throws IOException{ RandomAccessFile raf = new RandomAccessFile("test1.txt","rw"); raf.writeChars("中国你好"); raf.close(); } public String randRead() throws IOException{ RandomAccessFile raf = new RandomAccessFile("test1.txt","r"); StringBuffer sb = new StringBuffer(); while( raf.getFilePointer() < raf.length()){ sb.append( raf.readChar() ); } raf.close(); return sb.toString(); } 但是这样处理起来不是很方便,我们可以这样写: public void randWrite() throws IOException{ RandomAccessFile raf = new RandomAccessFile("test1.txt","rw"); raf.writeUTF("中国你好"); raf.close(); } public String randRead() throws Exception{ RandomAccessFile raf = new RandomAccessFile("test1.txt","r"); String str = raf.readUTF(); raf.close(); return str; } 好了,文件读写的中文问题就解决了。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!405.entry
1. Abstract: Java将I/O分为高阶I/O与低阶I/O,高阶I/O在使用上提供更多的读写方法,如读写 int、double、String的资料型态,而低阶的I/O大部份只提供write、read的byte[]存取,因为程式大部份的资料都是以字串或 其它主要型态资料来运算,因此低阶的I/O在使用上不利於程式设计,所以Java将许多好用的方法全部集合成高阶I/O; 换言之,低阶I/O的主要工作是负责与媒体资料作存取,高阶I/O类别主要作资料型态的转换及提供一些特殊的功能。在使用Java I/O时要谨记的一个重要原则是,在建立一个I/O之前必需先用低阶I/O类别来存取媒体资料(如档案或pipe),之後再使用高阶I/O来控制低阶 I/O类别的动作,这种一层又一层的架构称I/O Chain。底下为Java的I/O架构图,第一个为以byte为单位的I/O,第二个则是以char为单位。 2. File I/O: A. FileInputStream & FileOutputStream FileInputStream是读取档案用的类别,其建构式有叁个: public FileInputStream(String strFilename) throws FileNotFoundException public FileInputStream(File fIn) throws FileNotFoundException public FileInputStream(FileDescriptor fdObj) 在这里我只讲第一个,这是最直觉的方式,如下的范例1,会一次从e:\test.txt读10个bytes,将读入的结果输出到标准输出设备,直到档案结 束。在这个范例中要注意的是,available会传回输入串流中还有多少个bytes,read则会根据buffer的大小来决定一次读几个 bytes,并将实际读到的byte数传回。 ===== 范例 1 ===== import java.io.*; public class FIn { public FIn() { try { FileInputStream fis = new FileInputStream("e:/in.txt"); while (fis.available() > 0) { byte[] b = new byte[10]; int nResult = fis.read(b); if (nResult == -1) break; System.out.println(new String(b)); } fis.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { FIn fIn = new FIn(); } } FileOutputStream是写入档案用的类别,其建构式有四个: Public FileOutputStream(String strFilename) throws FileNotFoundException Public FileOutputStream(File fOut) throws FileNotFound Exception Public FileOutputStream(FileDescriptor fdObj) public FileOutputStream(String name, boolean append) throws FileNotFoundException 第四个和第一个的差别只在於当档案存在时,第一个会将原来的档案内容覆盖,第四个则可以选择覆盖或将新内容接在原内容後面。范例2以建构式一讲解如何写入 一个档案…在这个范例中要注意的是,fIn每个读10个bytes,但是最後一次不一定会读10个bytes,因此,fOut在write时,要指明要写 几个bytes到档案中,否则最後一次仍会写入10个bytes,因Java在new byte时会先将内容先填0,所以後面的几个bytes会是0。 ===== 范例2 ===== import java.io.*; public class FOut { public FOut() { try { FileInputStream fIn = new FileInputStream("e:/in.txt"); FileOutputStream fOut = new FileOutputStream("e:/out.txt"); while (fIn.available() > 0) { byte[] b = new byte[10]; int nResult = fIn.read(b); if (nResult == -1) break; fOut.write(b, 0, nResult); } fIn.close(); fOut.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { FOut FOut1 = new FOut(); } } B. FileReader & FileWriter FileReader 和FileInputStream最大不同在於,FileInputStream读取的单位是byte,FileReader读取的单位是char。另外 要注意的是,在FileInputStream中以available来判断是否还有资料可读取,在FileReader中是以ready来判断, 但是,available是传回还有多少个bytes可以读取,ready则传回true或false,当传回true时表示,下次read时保证不会停顿,当传回false时,表示下次read时”可能”停顿,所谓可能是指不保证不会停顿。 Ps. 测试时,in.txt里放些中文字就可以看出以byte和以char为单位有什麽不同。 ===== 范例 3 ===== import java.io.*; public class chFIn { public chFIn() { try { FileReader rdFile = new FileReader("e:/in.txt"); while (rdFile.ready()) { char[] chIn = new char[10]; int nResult = rdFile.read(chIn); if (nResult == -1) break; System.out.println(chIn); } rdFile.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { chFIn chFIn1 = new chFIn(); } } FileWriter和FileOutputStream的最大不同也在於写入单位的不同,FileOutputStream为byte,FileWriter为char。 ===== 范例 4 ===== import java.io.*; public class chFOut { public chFOut() { try { FileReader rdFile = new FileReader("e:/in.txt"); FileWriter wrFile = new FileWriter("e:/out.txt"); while (rdFile.ready()) { char[] chIn = new char[10]; int nResult = rdFile.read(chIn); if (nResult == -1) break; wrFile.write(chIn, 0, nResult); } rdFile.close(); wrFile.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { chFOut chFOut1 = new chFOut(); } } C. BufferedReader & BufferedWriter File I/O是相当耗时的作业,通常电脑在做处理时,者会建立一个缓冲区,一次读取或写入一个区块,借由减少I/O次数,来节省时间 ,在Java中的BufferedReader和BufferedWriter就是提供这样的缓冲功能。 在 范例5中,我们将FileReader导向BufferedReader,将FileWriter导向BufferedWriter,来达到区块读取、写 入的目的。BufferedReader提供的readLine一次可以读取一行,当遇到档尾时,会传回null。BufferedWriter提供的 newLine会产生列尾符号,这个列尾符号随作业系统的不同而不同,在Windows上为\r\n,在Unix上为\n,在Mac上为\r,这个符号是 依据line.separator系统性质而来的。需注意的是,如果将BufferedWriter应用到网路程式时,绝对不要使用newLine,因为 绝大多数的网路协定都是以\r\n为列尾,不会因作业系统不同而异。 ===== 范例 5 ===== import java.io.*; public class bufIn { public bufIn() { try { FileReader rdFile = new FileReader("e:/in.txt"); BufferedReader brdFile = new BufferedReader(rdFile); FileWriter wrFile = new FileWriter("e:/out.txt"); BufferedWriter bwrFile = new BufferedWriter(wrFile); String strLine; while ((strLine = brdFile.readLine()) != null) { bwrFile.write(strLine); bwrFile.newLine(); } brdFile.close(); bwrFile.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { bufIn bufIn1 = new bufIn(); } } D. File 在档案处理方面,程式不只是要对档案做读、写,有时也需要得知档案的属性,或删除、移动、更名,有时则是要找出或列出某目录下的某些档案,针对这些运作,Java提供了File这个类别。底下的范例,说明如何使用File类别。 a. 如何得知档案属性: 在 范例6中需注意的是lastModified传回的最後更改时间是自1970/1/1 00:00:00算起的时间,单位为毫秒,所以要用Date将它转换成日期、时间; 另外getCanonicalPath和getAbsolutePath得到的值在Windows上会是一样的,在Unix可能就会不一样。 ===== 范例 6 ===== import java.io.*; import java.util.*; public class FileSpy { public FileSpy(String strFilename) { File fFile = new File(strFilename); if (fFile.exists()) { System.out.println("Name: " + fFile.getName()); System.out.println("Absolute path: " + fFile.getAbsolutePath()); try { System.out.println("Canonical path: " + fFile.getCanonicalPath()); } catch (IOException e) { e.printStackTrace(); } if (fFile.canWrite()) System.out.println(fFile.getName() + " is writable"); if (fFile.canRead()) System.out.println(fFile.getName() + " is readable"); if (fFile.isFile()) { System.out.println(fFile.getName() + " is a file"); } else if (fFile.isDirectory()) { System.out.println(fFile.getName() + " is a directory"); } else { System.out.println("What is this?"); } long lngMilliseconds = fFile.lastModified(); if (lngMilliseconds !=0) System.out.println("last modified at " + new Date(lngMilliseconds)); long lngLen = fFile.length(); if (lngLen !=0) System.out.println("size: " + lngLen); } else System.out.println("file not found"); } public static void main(String[] args) { if (args.length == 1) { FileSpy fileSpy1 = new FileSpy(args[0]); } else System.out.println("Usage: java FileSpy Filename"); } } b. 建立、删除、移动、更名: File 类别提供了createNewFile、renameTo、delete作为建立(createNewFile)、删除(delete)、移动、更名 (renameTo)之用,使用方式如下: (移动和更名都用renameTo,就如在Unix上档案搬移和更名都用mv一样) ===== 范例 7 ===== import java.io.*; public class OperateFile { public OperateFile() { //create new file File fNewFile = new File("C:/newfile.txt"); try { if (fNewFile.exists() == false) { if (fNewFile.createNewFile() == true) { System.out.println("create c:/newfile.txt success"); } else { System.out.println("create c:/newfile.txt fail"); } } else { System.out.println("file already exists"); } } catch (IOException e) { e.printStackTrace(); } //rename file File fRenameFile = new File("c:/renamefile.txt"); fNewFile.renameTo(fRenameFile); //remove file File fRemoveFile = new File("d:/" + fRenameFile.getName()); fRenameFile.renameTo(fRemoveFile); //delete file try { File fDelFile = new File(fRemoveFile.getCanonicalPath()); fDelFile.delete(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { OperateFile operateFile1 = new OperateFile(); } } c. 找出某特定目录里的所有档案: File类别提供的list和listFiles都可以列出某特定目录里的所有档案,其中list传回的是String[],listFiles传回的是File[],这两个函式都会传回所有的档案和目录。 ===== 范例 8 ===== import java.io.*; public class ListAllFiles { public ListAllFiles(String strDir) { File fDir = new File(strDir); File[] fAllFiles = fDir.listFiles(); for(int i=0; i if (fAllFiles.isFile()) System.out.println("File: " + fAllFiles.getName()); else System.out.println("Dir: " + fAllFiles.getName()); } } public static void main(String[] args) { ListAllFiles listAllFiles1 = new ListAllFiles(args[0]); } } 3. Network I/O: Java对网路的支援只有TCP/IP和UDP/IP,提供的类别有URL、URLConnection、Socket、ServerSocket,在这里我只打算用ServerSocket、Socket为例,来说明Network I/O。 基本上,Java的I/O不管在任何的媒体上都是将它们视为stream,所以,网路I/O和档案I/O原理也是一致的,底下的两个程式分别为server socket及client socket。在看范例之前,可以再复习一下前面的abstract… ===== 范例 9 ===== import java.net.*; import java.io.*; public class myServer { public myServer(String strPort) { int nPort = new Integer(strPort).intValue(); try { ServerSocket ss = new ServerSocket(nPort); Socket s = ss.accept(); OutputStream out = s.getOutputStream(); PrintStream psOut = new PrintStream(out); String strResponse = "Hello " + s.getInetAddress() + " on port " + s.getPort() + "\r\n"; strResponse += "This is " + s.getLocalAddress() + " on port " + s.getLocalPort() + "\r\n"; psOut.print(strResponse); s.close(); ss.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { myServer myServer1 = new myServer(args[0]); } } ===== 范例 10 ===== import java.net.*; import java.io.*; public class myClient { public myClient(String strIP, String strPort) { int nPort = new Integer(strPort).intValue(); try { Socket s = new Socket(strIP, nPort); InputStream in = s.getInputStream(); BufferedInputStream bisIn = new BufferedInputStream(in); while (bisIn.available() > 0) { byte[] b = new byte[30]; int nLen = bisIn.read(b); System.out.println(new String(b, 0, nLen)); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { myClient myClient1 = new myClient(args[0], args[1]); } } 4. Object Serialization: A. 所谓Object Serialization就是把物件的”状态”储存成一系列的位元组,而这些位元组在稍候可用来恢复物件。更简单的说,Object Serialization是让物件可以以物件为储存单位。在Java中,任何物件要能Serialization,必须implements Serializable这个Interface,以下是一个简单的程式范例,可以将物件储存到e:\point.ser,或从e:\point.ser 将物件恢复原值。 ===== 范例 11 ===== import java.io.*; public class ThreeDPoint implements Serializable { private double m_dblX, m_dblY, m_dblZ; public ThreeDPoint(double x, double y, double z) { m_dblX = x; m_dblY = y; m_dblZ = z; } public void PrintXYZ() { System.out.println("X: " + m_dblX); System.out.println("Y: " + m_dblY); System.out.println("Z: " + m_dblZ); } public static void main(String[] args) { if (args[0].equalsIgnoreCase("w")) { ThreeDPoint threeDPoint1 = new ThreeDPoint(10 ,20, 30); try { FileOutputStream fout = new FileOutputStream("e:\\point.ser"); ObjectOutputStream oout = new ObjectOutputStream(fout); oout.writeObject(threeDPoint1); oout.close(); System.out.println("write:"); threeDPoint1.PrintXYZ(); } catch (Exception e) { e.printStackTrace(); } } else if (args[0].equalsIgnoreCase("r")) { try { FileInputStream fin = new FileInputStream("e:\\point.ser"); ObjectInputStream oin = new ObjectInputStream(fin); Object o = oin.readObject(); ThreeDPoint threeDPoint1 = (ThreeDPoint) o; oin.close(); System.out.println("read:"); threeDPoint1.PrintXYZ(); } catch (Exception e) { } } } //end of main } B. 在Java中,一个实作某特定介面的类别,其子类别也因继承的原故而被视为实作了该介面,因此,许多没有明确宣告实作Serializable介面的类别,事实上也是可以被Serialization的。 C. 并非每个实作了Serializable介面的物件都可以被Serialization,如果这个物件继承图上的祖先,有其中一个是不可以被Serialization,那麽这个物件就不可以被Serialization。 5. Formated I/O: 在Java 的I/O里,并没有所谓的型别,不管是int、long、double…最後都是以String输出,所以如果要让数字以特定格式输出,需透过Java提 供的两个类别java.text.NumberFormat和java.text.DecimalFormat将数字格式化後再输出。 范例12简 要说明NumberFormat如何使用,在开始使用NumberFormat时,应先用getInstance取得NumberFormat的实体,范 例12中的setMaximumIntegerDigits和setMinimumFractionDigits是用来设定整数和小数的位数,另外还有 setMinimumIntegerDigits和setMaximumFractionDigits也是同样功能。这些设定如有冲突,Java以最後设 定的为准。 ===== 范例 12 ===== import java.text.*; public class myFormat { public myFormat() { NumberFormat nf = NumberFormat.getInstance(); double dblNum = Math.PI; System.out.println(dblNum); nf.setMaximumIntegerDigits(5); nf.setMinimumFractionDigits(4); System.out.println("PI: " + nf.format(dblNum)); } public static void main(String[] args) { myFormat myFormat1 = new myFormat(); } } 另 一个类别DecimalFormat是继承NumberFormat的子类别,它提供了更强的格式化功能,透过设定pattern,可以使我们的输出更多 样化,至於Java提供的pattern有那些? 在API Document中有详细说明! 范例13仅举其一,说明DecimalFormat如何使用。 ===== 范例 13 ===== import java.text.*; public class myDecimalFormat { public myDecimalFormat() { DecimalFormat df = new DecimalFormat("0000.000"); double dblNum = 123.45; System.out.println("dblNum: " + dblNum); System.out.println("dblNum: " + df.format(dblNum)); } public static void main(String[] args) { myDecimalFormat myDecimalFormat1 = new myDecimalFormat(); } } 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!399.entry
一 字符型函数 SQL中的单记录函数 1.ASCII 返回与指定的字符对应的十进制数; SQL> select ascii('A') A,ascii('a') a,ascii('0') zero,ascii(' ') space from dual; A A ZERO SPACE --------- --------- --------- --------- 65 97 48 32 2.CHR 给出整数,返回对应的字符; SQL> select chr(54740) zhao,chr(65) chr65 from dual; ZH C -- - 赵 A 3.CONCAT 连接两个字符串; SQL> select concat('010-','88888888')||'转23' 高乾竞电话 from dual; 高乾竞电话 ---------------- 010-88888888转23 4.INITCAP 返回字符串并将字符串的第一个字母变为大写; SQL> select initcap('smith') upp from dual; UPP ----- Smith 5.INSTR(C1,C2,I,J) 在一个字符串中搜索指定的字符,返回发现指定的字符的位置; C1 被搜索的字符串 C2 希望搜索的字符串 I 搜索的开始位置,默认为1 J 出现的位置,默认为1 SQL> select instr('oracle traning','ra',1,2) instring from dual; INSTRING --------- 9 6.LENGTH 返回字符串的长度; SQL> select name,length(name),addr,length(addr),sal,length(to_char(sal)) from .nchar_tst; NAME LENGTH(NAME) ADDR LENGTH(ADDR) SAL LENGTH(TO_CHAR(SAL)) ------ ------------ ---------------- ------------ --------- -------------------- 高乾竞 3 北京市海锭区 6 9999.99 7 7.LOWER 返回字符串,并将所有的字符小写 SQL> select lower('AaBbCcDd')AaBbCcDd from dual; AABBCCDD -------- aabbccdd 8.UPPER 返回字符串,并将所有的字符大写 SQL> select upper('AaBbCcDd') upper from dual; UPPER -------- AABBCCDD 9.RPAD和LPAD(粘贴字符) RPAD 在列的右边粘贴字符 LPAD 在列的左边粘贴字符 SQL> select lpad(rpad('gao',10,'*'),17,'*')from dual; LPAD(RPAD('GAO',1 ----------------- *******gao******* 不够字符则用*来填满 10.LTRIM和RTRIM LTRIM 删除左边出现的字符串 RTRIM 删除右边出现的字符串 SQL> select ltrim(rtrim(' gao qian jing ',' '),' ') from dual; LTRIM(RTRIM(' ------------- gao qian jing 11.SUBSTR(string,start,count) 取子字符串,从start开始,取count个 SQL> select substr('13088888888',3,8) from dual; SUBSTR(' -------- 08888888 12.REPLACE('string','s1','s2') string 希望被替换的字符或变量 s1 被替换的字符串 s2 要替换的字符串 SQL> select replace('he love you','he','i') from dual; REPLACE('HELOVEYOU','HE','I') ------------------------------ i love you 13.SOUNDEX 返回一个与给定的字符串读音相同的字符串 SQL> create table table1(xm varchar(8)); SQL> insert into table1 values('weather'); SQL> insert into table1 values('wether'); SQL> insert into table1 values('gao'); SQL> select xm from table1 where soundex(xm)=soundex('weather'); XM -------- weather wether 14.TRIM('s' from 'string') LEADING 剪掉前面的字符 TRAILING 剪掉后面的字符 如果不指定,默认为空格符 二 数字型函数 15.ABS 返回指定值的绝对值 SQL> select abs(100),abs(-100) from dual; ABS(100) ABS(-100) --------- --------- 100 100 16.ACOS 给出反余弦的值 SQL> select acos(-1) from dual; ACOS(-1) --------- 3.1415927 17.ASIN 给出反正弦的值 SQL> select asin(0.5) from dual; ASIN(0.5) --------- .52359878 18.ATAN 返回一个数字的反正切值 SQL> select atan(1) from dual; ATAN(1) --------- .78539816 19.CEIL 返回大于或等于给出数字的最小整数 SQL> select ceil(3.1415927) from dual; CEIL(3.1415927) --------------- 4 20.COS 返回一个给定数字的余弦 SQL> select cos(-3.1415927) from dual; COS(-3.1415927) --------------- -1 21.COSH 返回一个数字反余弦值 SQL> select cosh(20) from dual; COSH(20) --------- 242582598 22.EXP 返回一个数字e的n次方根 SQL> select exp(2),exp(1) from dual; EXP(2) EXP(1) --------- --------- 7.3890561 2.7182818 23.FLOOR 对给定的数字取整数 SQL> select floor(2345.67) from dual; FLOOR(2345.67) -------------- 2345 24.LN 返回一个数字的对数值 SQL> select ln(1),ln(2),ln(2.7182818) from dual; LN(1) LN(2) LN(2.7182818) --------- --------- ------------- 0 .69314718 .99999999 25.LOG(n1,n2) 返回一个以n1为底n2的对数 SQL> select log(2,1),log(2,4) from dual; LOG(2,1) LOG(2,4) --------- --------- 0 2 26.MOD(n1,n2) 返回一个n1除以n2的余数 SQL> select mod(10,3),mod(3,3),mod(2,3) from dual; MOD(10,3) MOD(3,3) MOD(2,3) --------- --------- --------- 1 0 2 27.POWER 返回n1的n2次方根 SQL> select power(2,10),power(3,3) from dual; POWER(2,10) POWER(3,3) ----------- ---------- 1024 27 28.ROUND和TRUNC 按照指定的精度进行舍入 SQL> select round(55.5),round(-55.4),trunc(55.5),trunc(-55.5) from dual; ROUND(55.5) ROUND(-55.4) TRUNC(55.5) TRUNC(-55.5) ----------- ------------ ----------- ------------ 56 -55 55 -55 29.SIGN 取数字n的符号,大于0返回1,小于0返回-1,等于0返回0 SQL> select sign(123),sign(-100),sign(0) from dual; SIGN(123) SIGN(-100) SIGN(0) --------- ---------- --------- 1 -1 0 30.SIN 返回一个数字的正弦值 SQL> select sin(1.57079) from dual; SIN(1.57079) ------------ 1 31.SIGH 返回双曲正弦的值 SQL> select sin(20),sinh(20) from dual; SIN(20) SINH(20) --------- --------- .91294525 242582598 32.SQRT 返回数字n的根 SQL> select sqrt(64),sqrt(10) from dual; SQRT(64) SQRT(10) --------- --------- 8 3.1622777 33.TAN 返回数字的正切值 SQL> select tan(20),tan(10) from dual; TAN(20) TAN(10) --------- --------- 2.2371609 .64836083 34.TANH 返回数字n的双曲正切值 SQL> select tanh(20),tan(20) from dual; TANH(20) TAN(20) --------- --------- 1 2.2371609 35.TRUNC 按照指定的精度截取一个数 SQL> select trunc(124.1666,-2) trunc1,trunc(124.16666,2) from dual; TRUNC1 TRUNC(124.16666,2) --------- ------------------ 100 124.16 60.AVG(DISTINCT|ALL) all表示对所有的值求平均值,distinct只对不同的值求平均值 SQLWKS> create table table3(xm varchar(8),sal number(7,2)); 语句已处理。 SQLWKS> insert into table3 values('gao',1111.11); SQLWKS> insert into table3 values('gao',1111.11); SQLWKS> insert into table3 values('zhu',5555.55); SQLWKS> commit; SQL> select avg(distinct sal) from gao.table3; AVG(DISTINCTSAL) ---------------- 3333.33 SQL> select avg(all sal) from gao.table3; AVG(ALLSAL) ----------- 2592.59 61.MAX(DISTINCT|ALL) 求最大值,ALL表示对所有的值求最大值,DISTINCT表示对不同的值求最大值,相同的只取一次 SQL> select max(distinct sal) from scott.emp; MAX(DISTINCTSAL) ---------------- 5000 62.MIN(DISTINCT|ALL) 求最小值,ALL表示对所有的值求最小值,DISTINCT表示对不同的值求最小值,相同的只取一次 SQL> select min(all sal) from gao.table3; 63.STDDEV(distinct|all) 求标准差,ALL表示对所有的值求标准差,DISTINCT表示只对不同的值求标准差 SQL> select stddev(sal) from scott.emp; STDDEV(SAL) ----------- 1182.5032 SQL> select stddev(distinct sal) from scott.emp; STDDEV(DISTINCTSAL) ------------------- 1229.951 64.VARIANCE(DISTINCT|ALL) 求协方差 SQL> select variance(sal) from scott.emp; VARIANCE(SAL) ------------- 1398313.9 三 日期型函数 36.ADD_MONTHS 增加或减去月份 SQL> select to_char(add_months(to_date('199912','yyyymm'),2),'yyyymm') from dual; TO_CHA ------ 200002 SQL> select to_char(add_months(to_date('199912','yyyymm'),-2),'yyyymm') from dual; TO_CHA ------ 199910 37.LAST_DAY 返回日期的最后一天 SQL> select to_char(sysdate,'yyyy.mm.dd'),to_char((sysdate)+1,'yyyy.mm.dd') from dual; TO_CHAR(SY TO_CHAR((S ---------- ---------- 2004.05.09 2004.05.10 SQL> select last_day(sysdate) from dual; LAST_DAY(S ---------- 31-5月 -04 38.MONTHS_BETWEEN(date2,date1) 给出date2-date1的月份 SQL> select months_between('19-12月-1999','19-3月-1999') mon_between from dual; MON_BETWEEN ----------- 9 SQL>selectmonths_between(to_date('2000.05.20','yyyy.mm.dd'),to_date('2005.05.20','yyyy.dd')) mon_betw from dual; MON_BETW --------- -60 39.NEW_TIME(date,'this','that') 给出在this时区=other时区的日期和时间 SQL> select to_char(sysdate,'yyyy.mm.dd hh24:mi:ss') bj_time,to_char(new_time 2 (sysdate,'PDT','GMT'),'yyyy.mm.dd hh24:mi:ss') los_angles from dual; BJ_TIME LOS_ANGLES ------------------- ------------------- 2004.05.09 11:05:32 2004.05.09 18:05:32 40.NEXT_DAY(date,'day') 给出日期date和星期x之后计算下一个星期的日期 SQL> select next_day('18-5月-2001','星期五') next_day from dual; NEXT_DAY ---------- 25-5月 -01 41.SYSDATE 用来得到系统的当前日期 SQL> select to_char(sysdate,'dd-mm-yyyy day') from dual; TO_CHAR(SYSDATE,' ----------------- 09-05-2004 星期日 trunc(date,fmt)按照给出的要求将日期截断,如果fmt='mi'表示保留分,截断秒 SQL> select to_char(trunc(sysdate,'hh'),'yyyy.mm.dd hh24:mi:ss') hh, 2 to_char(trunc(sysdate,'mi'),'yyyy.mm.dd hh24:mi:ss') hhmm from dual; HH HHMM ------------------- ------------------- 2004.05.09 11:00:00 2004.05.09 11:17:00 四 转换类函数 42.CHARTOROWID 将字符数据类型转换为ROWID类型 SQL> select rowid,rowidtochar(rowid),ename from scott.emp; ROWID ROWIDTOCHAR(ROWID) ENAME ------------------ ------------------ ---------- AAAAfKAACAAAAEqAAA AAAAfKAACAAAAEqAAA SMITH AAAAfKAACAAAAEqAAB AAAAfKAACAAAAEqAAB ALLEN AAAAfKAACAAAAEqAAC AAAAfKAACAAAAEqAAC WARD AAAAfKAACAAAAEqAAD AAAAfKAACAAAAEqAAD JONES 43.CONVERT(c,dset,sset) 将源字符串 sset从一个语言字符集转换到另一个目的dset字符集 SQL> select convert('strutz','we8hp','f7dec') "conversion" from dual; conver ------ strutz 44.HEXTORAW 将一个十六进制构成的字符串转换为二进制 45.RAWTOHEXT 将一个二进制构成的字符串转换为十六进制 46.ROWIDTOCHAR 将ROWID数据类型转换为字符类型 47.TO_CHAR(date,'format') SQL> select to_char(sysdate,'yyyy/mm/dd hh24:mi:ss') from dual; TO_CHAR(SYSDATE,'YY ------------------- 2004/05/09 21:14:41 48.TO_DATE(string,'format') 将字符串转化为ORACLE中的一个日期 49.TO_MULTI_BYTE 将字符串中的单字节字符转化为多字节字符 SQL> select to_multi_byte('高') from dual; TO -- 高 50.TO_NUMBER 将给出的字符转换为数字 SQL> select to_number('1999') year from dual; YEAR --------- 1999 51.BFILENAME(dir,file) 指定一个外部二进制文件 SQL>insert into file_tb1 values(bfilename('lob_dir1','image1.gif')); 52.CONVERT('x','desc','source') 将x字段或变量的源source转换为desc SQL> select sid,serial#,username,decode(command, 2 0,'none', 3 2,'insert', 4 3, 5 'select', 6 6,'update', 7 7,'delete', 8 8,'drop', 9 'other') cmd from v$session where type!='background'; SID SERIAL# USERNAME CMD --------- --------- ------------------------------ ------ 1 1 none 2 1 none 3 1 none 4 1 none 5 1 none 6 1 none 7 1275 none 8 1275 none 9 20 GAO select 10 40 GAO none 53.DUMP(s,fmt,start,length) DUMP函数以fmt指定的内部数字格式返回一个VARCHAR2类型的值 SQL> col global_name for a30 SQL> col dump_string for a50 SQL> set lin 200 SQL> select global_name,dump(global_name,1017,8,5) dump_string from global_name; GLOBAL_NAME DUMP_STRING ------------------------------ -------------------------------------------------- ORACLE.WORLD Typ=1 Len=12 CharacterSet=ZHS16GBK: W,O,R,L,D 五 系统函数 54.EMPTY_BLOB()和EMPTY_CLOB() 这两个函数都是用来对大数据类型字段进行初始化操作的函数 55.GREATEST 返回一组表达式中的最大值,即比较字符的编码大小. SQL> select greatest('AA','AB','AC') from dual; GR -- AC SQL> select greatest('啊','安','天') from dual; GR -- 天 56.LEAST 返回一组表达式中的最小值 SQL> select least('啊','安','天') from dual; LE -- 啊 57.UID 返回标识当前用户的唯一整数 SQL> show user USER 为"GAO" SQL> select username,user_id from dba_users where user_id=uid; USERNAME USER_ID ------------------------------ --------- GAO 25 58.USER 返回当前用户的名字 SQL> select user from dual; USER ------------------------------ GAO 59.USEREVN 返回当前用户环境的信息,opt可以是: ENTRYID,SESSIONID,TERMINAL,ISDBA,LABLE,LANGUAGE,CLIENT_INFO,LANG,VSIZE ISDBA 查看当前用户是否是DBA如果是则返回true SQL> select userenv('isdba') from dual; USEREN ------ FALSE SQL> select userenv('isdba') from dual; USEREN ------ TRUE SESSION 返回会话标志 SQL> select userenv('sessionid') from dual; USERENV('SESSIONID') -------------------- 152 ENTRYID 返回会话人口标志 SQL> select userenv('entryid') from dual; USERENV('ENTRYID') ------------------ 0 INSTANCE 返回当前INSTANCE的标志 SQL> select userenv('instance') from dual; USERENV('INSTANCE') ------------------- 1 LANGUAGE 返回当前环境变量 SQL> select userenv('language') from dual; USERENV('LANGUAGE') ---------------------------------------------------- SIMPLIFIED CHINESE_CHINA.ZHS16GBK LANG 返回当前环境的语言的缩写 SQL> select userenv('lang') from dual; USERENV('LANG') ---------------------------------------------------- ZHS TERMINAL 返回用户的终端或机器的标志 SQL> select userenv('terminal') from dual; USERENV('TERMINA ---------------- GAO VSIZE(X) 返回X的大小(字节)数 SQL> select vsize(user),user from dual; VSIZE(USER) USER ----------- ------------------------------ 6 SYSTEM 六 统计类函数 65.GROUP BY 主要用来对一组数进行统计 SQL> select deptno,count(*),sum(sal) from scott.emp group by deptno; DEPTNO COUNT(*) SUM(SAL) --------- --------- --------- 10 3 8750 20 5 10875 30 6 9400 66.HAVING 对分组统计再加限制条件 SQL> select deptno,count(*),sum(sal) from scott.emp group by deptno having nt(*)>=5; DEPTNO COUNT(*) SUM(SAL) --------- --------- --------- 20 5 10875 30 6 9400 SQL> select deptno,count(*),sum(sal) from scott.emp having count(*)>=5 group by tno ; DEPTNO COUNT(*) SUM(SAL) --------- --------- --------- 20 5 10875 30 6 9400 67.ORDER BY 用于对查询到的结果进行排序输出 SQL> select deptno,ename,sal from scott.emp order by deptno,sal desc; DEPTNO ENAME SAL --------- ---------- --------- 10 KING 5000 10 CLARK 2450 10 MILLER 1300 20 SCOTT 3000 20 FORD 3000 20 JONES 2975 20 ADAMS 1100 20 SMITH 800 30 BLAKE 2850 30 ALLEN 1600 30 TURNER 1500 30 WARD 1250 30 MARTIN 1250 30 JAMES 950 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!397.entry
====第一篇==开始========================================================================== 总结: 重复提交、重复刷新、防止后退的问题以及处理方式 一。前言 你在任何一个比较专业的BBS都会看到这样的问题,即使你Google一下,也会发现有很多的人在关注和询问,但大家给出的解决方法却都是千差万别,(有的人主张采用脚本来解决;有的则想重定向到别的页面;有的则将此问题提升到Token的角度)为什么会有如此大的差异呢? 二。问题场景 首先,我们应该先了解为什么要处理这样的问题?或者专业一点就是它适合的场景是什么?(似乎只有人来问没有人来解释) 1。重复提交、重复刷新的场景 重复提交、重复刷新都是来解决系统重复记录的问题。也就是说某个人在多次的提交某条记录(为什么?也许是闲了没有事情干的;最有可能是用户根本就不知道自己的提交结果是否已经执行了?!)。 但出现了这样的问题并不见得就必须处理,要看你所开发的系统的类别而定。比如你接手的是某个资源管理系统,系统本身从需求的角度根本就不允许出现"重复"的记录,在这样需求的约束条件下,去执行重复的提交动作只会引发“业务级异常”的产生,根本就不可能执行成功也就无所谓避免不避免的问题了。 2。防止后退的场景 了解了重复刷新、重复提交的场景,我们来了解一下"防止后退"操作的原因是什么?比如你在开发某个投票系统,它有很多的步骤,并且这些步骤之间是有联系的,比如第一步会将某些信息发送给第二步,第二步缓存了这些信息,同时将自身的信息发送给了第三步。。。。。等等,如果此时用户处在第三步骤下,我们想象一下某个淘气用户的用户点击了后退按钮,此时屏幕出现了第二步骤的页面,他再次的修改或者再次的提交,进入到下一个步骤(也就是第三步骤),错误就会在此产生?!什么错误呢?最为典型的就是这样的操作直接导致了对于第一个步骤信息的丢失!(如果这样的信息是依靠Request存放的话,当然你可以存放在Session或者更大的上下文环境中,但这不是个好主意!关于信息存放的问题,下次在就这个问题详细的讨论) 三。如何处理的问题 当然很多的系统(比如订票系统从需求上本身是允许个人重复订票的)是必须要避免重复刷新、重复提交、以及防止后退的问题的,但即使是这样的问题,也要区分如何处理以及在哪里处理的(网上只是告诉你如何处理,但很少去区分在哪里处理的),显然处理的方式无非是客户端或者服务器端两种,而面对不同的位置处理的方式也是不同的,但有一点要事先声明:任何客户端(尤其是B/S端)的处理都是不可信任的,最好的也是最应该的是服务器端的处理方法。 客户端处理: 面对客户端我们可以使用Javascript脚本来解决,如下 1。重复刷新、重复提交 Ways One:设置一个变量,只允许提交一次。 <script language="javascript"> var checkSubmitFlg = false; function checkSubmit() { if (checkSubmitFlg == true) { return false; } checkSubmitFlg = true; return true; } document.ondblclick = function docondblclick() { window.event.returnValue = false; } document.onclick = function doconclick() { if (checkSubmitFlg) { window.event.returnValue = false; } } </script> <html:form action="myAction.do" method="post" onsubmit="return checkSubmit();"> Way Two : 将提交按钮或者image置为disable <html:form action="myAction.do" method="post" onsubmit="getElById('submitInput').disabled = true; return true;"> <html:image styleId="submitInput" src="images/ok_b.gif" border="0" /> </html:form> 2。防止用户后退 这里的方法是千姿百态,有的是更改浏览器的历史纪录的,比如使用window.history.forward()方法;有的是“用新页面的URL替换当前的历史纪录,这样浏览历史记录中就只有一个页面,后退按钮永远不会变为可用。”比如使用javascript:location.replace(this.href); event.returnValue=false; 2.服务器端的处理(这里只说Struts框架的处理) 利用同步令牌(Token)机制来解决Web应用中重复提交的问题,Struts也给出了一个参考实现。 基本原理: 服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较, 看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给 客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次 提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。 if (isTokenValid(request, true)) { // your code here return mapping.findForward("success"); } else { saveToken(request); return mapping.findForward("submitagain"); } Struts根据用户会话ID和当前系统时间来生成一个唯一(对于每个会话)令牌的,具体实现可以参考 TokenProcessor类中的generateToken()方法。 1. //验证事务控制令牌,<html:form >会自动根据session中标识生成一个隐含input代表令牌,防止两次提交 2. 在action中: //<input type="hidden" name="org.apache.struts.taglib.html.TOKEN" // value="6aa35341f25184fd996c4c918255c3ae"> if (!isTokenValid(request)) errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.transaction.token")); resetToken(request); //删除session中的令牌 3. action有这样的一个方法生成令牌 protected String generateToken(HttpServletRequest request) { HttpSession session = request.getSession(); try { byte id[] = session.getId().getBytes(); byte now[] = new Long(System.currentTimeMillis()).toString().getBytes(); MessageDigest md = MessageDigest.getInstance("MD5"); md.update(id); md.update(now); return (toHex(md.digest())); } catch (IllegalStateException e) { return (null); } catch (NoSuchAlgorithmException e) { return (null); } } 总结 对于重复提交、重复刷新、防止后退等等都是属于系统为避免重复记录而需要解决的问题,在客户端去处理需要针对每一种的可能提出相应的解决方案,然而在服务器端看来只不过是对于数据真实性的检验问题,基于令牌的处理就是一劳永逸的方法。 同时我们也看到,从不同的角度去看待问题,其解决的方法也是不同的。客户端更追求的是用户的操作,而服务端则将注意力放在了数据的处理上,所以在某个对于服务器端看似容易的问题上,用客户端来解决却麻烦了很多!反之依然。所以在某些问题的处理上我们需要综合考虑和平衡,是用客户端来解决?还是用服务器端来处理?
====第一篇==结束========================================================================== ====第二篇==开始========================================================================== 网页防刷新重复提交、防后退解决方法 提交后禁用提交按钮(大部分人都是这样做的) 如果客户提交后,按F5刷新怎么办? 使用Session 在提交的页面也就是数据库处理之前: if session("ok")=true then response.write "错误,正在提交" response.end end if 数据处理完后,修改session("ok")=false。 数据处理成功马上Redirect到另外一个页面 操作后刷新的确是个问题,你可以使用跳转页面、关闭本页面,如果是有参数据条件来控制的,那就应该好做了,可以直接修改window.location的值,把参数全部改掉,这样就差不多了。 缺点:简单地运用Response.Redirect将不再有效,因为用户从一个页面转到另一个页面,我们都必须用客户端代码清除location.history。注意,这种方法清除的是最后一个访问历史记录,而不是全部的访问记录。 点击后退按钮,再点击后退按钮,你可以看到这时打开的是本页面之前的页面!(当然,这是在你的客户端启用了JavaScript功能的条件下。) 如果客户按后退,怎么办?
防止网页后退--禁止缓存 我们在进行数据库添加操作的时候,如果允许后退,而正巧有刷新了页面,就会再次执行添加操作,无疑这不是我们需要的,像一般网上很多禁止缓存的代码,有时并不可靠,这时你只要在操作的页面加上就可以了,在网页的里指定要定向的新页,再点后退,看是不是不会再退到刚才的操作页面了,实际上已经把这个历史给删除了 ASP: Response.Buffer = True Response.ExpiresAbsolute = Now() - 1 Response.Expires = 0 Response.CacheControl = "no-cache" ASP.NET: Response.Buffer=true; Response.ExpiresAbsolute=DateTime.Now.AddSeconds(-1); Response.Expires=0; Response.CacheControl="no-cache"; 究竟怎样才能"禁用"浏览器的后退按钮?或者“怎样才能防止用户点击后退按钮返回以前浏览过的页面?” 遗憾的是,我们无法禁用浏览器的后退按钮。 防止网页后退--新开窗口 用window.open弹出表单页面,点提交后关闭该页;处理提交的ASP页也是用弹出,设定表单的target,点提交时window.open("XXX.asp","_blank"),然后用JS来提交表单,完成后window.close(); 简单的说,就是提交表单的时候弹出新窗口,关闭本窗口。对于window.open()打开的窗口怎么后退?能后退到哪里去? 呵呵,罗嗦了一堆废话,知道怎么处理了么?混合运用客户端脚本和服务器端脚本。 ====第二篇==结束========================================================================== ====第三篇==开始========================================================================== 看了网上的,有几种方法: 1 在你的表单页里HEAD区加入这段代码: <META HTTP-EQUIV="pragma" CONTENT="no-cache"> <META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate"> <META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT"> 2 生成一个令牌保存在用户session中,在form中加一个hidden域,显示该令 牌的值,form提交后重新生成一个新的令牌,将用户提交的令牌和session 中的令牌比较,如相同则是重复提交 3 在你的服务器端控件的代码中使用Response.Redirect("selfPage")语句。但是大多的数都不使用这种方法。 方法还有很多。。。 4 <input type="button" value="提交" onclick="this.disabled=true;this.form.submit()"> 5 在JSP页面的FORM表单中添加一个hidden域 <input type="hidden" name="url"value=<%=request.getRequestURL()%>> 在你的serverlet中添加如下语句 String url=request.getParameter("url"); response.sendRedirect(url); 我一般都是采用这样的方法返回JSP页面的,不太明白你说的重复刷新是什么概念 6 ajax 无刷新提交 6.5 IFRAME无刷新提交: 表单提交的时候,提交到ifrmae页面中,本页面不刷新,然后在iframe页面中接收(象平常发送到第二页面一样接收),就可以了 <%@LANGUAGE="VBSCRIPT" CODEPAGE="936"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <title>无标题文档</title> </head> <body> <form name="form1" method="post" action="abc_update" target="upload"> <p> <input type="text" name="textfield"> </p> <p> <input type="text" name="textfield2"> </p> <p> <input type="submit" name="Submit" value="提交"> </p> </form> <iframe src="abc_update.asp" name=update height=0 width="0"></iframe> </body> </html> 7 Web开发中防止浏览器的刷新键引起系统操作重复提交 怎么解决呢?重定向可以解决页面刷新带来的数据的重复提交的问题,我们自然可以利用重定向的方式来解决这个问题。但是struts的action里面mapping.findword();跳转的话,默认的是在工程文件夹里面找要跳转的页面。这种情况,怎么解决呢? 修改struts-config.xml 文件, 在action里面有一个redirect重新定向的属性,struts中默认的是false,添加这个属性,改成true,在forword中写上要跳转页面的绝对或者相对地址就行了 修改如下: <action-mappings> <action attribute="newsActionForm" name="newsActionForm" input="/addnews.jsp" path="/newsAction" parameter="method" scope="request" type="com.yongtree.news.action.NewsAction"> <forward name="list" path="/listnews.jsp" redirect="true"></forward> <forward name="error" path="/addnews.jsp"></forward> </action> </action-mappings> ====第三篇==结束========================================================================== ====第四篇==开始========================================================================== 浏览器相关难处理的问题 浏览器的后退按钮使得我们能够方便地返回以前访问过的页面,它无疑非常有用。但有时候我们不得不关闭这个功能,以防止用户打乱预定的页面访问次序。本文介绍网络上可找到的各种禁用浏览器后退按钮方案,分析它们各自的优缺点和适用场合。 一、概述 曾经有许多人问起,“怎样才能‘禁用’浏览器的后退按钮?”,或者“怎样才能防止用户点击后退按钮返回以前浏览过的页面?”在ASP论坛上,这个问题也是问得最多的问题之一。遗憾的是,答案非常简单:我们无法禁用浏览器的后退按钮。 起先我对于居然有人想要禁用浏览器的后退按钮感到不可思议。后来,看到竟然有那么多的人想要禁用这个后退按钮,我也就释然(想要禁用的只有后退按钮,不包括浏览器的前进按钮)。因为在默认情况下,用户提交表单之后可以通过后退按钮返回表单页面(而不是使用“编辑”按钮!),然后再次编辑并提交表单向数据库插入新的记录。这是我们不愿看到的。 因此我就决定要找出避免出现这种情况的方法。我访问了许多网站,参考了这些网站所介绍的各种实现方法。如果你经常访问ASP编程网站,本文所介绍的部分内容你可能已经见到过。本文的任务是把各种可能的方法都介绍给大家,然后找出最好的方法! 二、禁止缓存 在我找到的许多方案中,其中有一种建议禁止页面缓存。具体是使用服务器端脚本,如下所示: <% Response.Buffer = True Response.ExpiresAbsolute = Now() - 1 Response.Expires = 0 Response.CacheControl = "no-cache" %> 这种方法非常有效!它强制浏览器重新访问服务器下载页面,而不是从缓存读取页面。使用这种方法时,编程者的主要任务是创建一个会话级的变量,通过这个变量确定用户是否仍旧可以查看那个不适合通过后退按钮访问的页面。由于浏览器不再缓存这个页面,当用户点击后退按钮时浏览器将重新下载该页面,此时程序就可以检查那个会话变量,看看是否应该允许用户打开这个页面。 例如,假设我们有如下表单: <% Response.Buffer = True Response.ExpiresAbsolute = Now() - 1 Response.Expires = 0 Response.CacheControl = "no-cache" If Len(Session("FirstTimeToPage")) > 0 then &single; 用户已经访问过当前页面,现在是再次返回访问。 &single; 清除会话变量,将用户重定向到登录页面。 Session("FirstTimeToPage") = "" Response.Redirect "/Bar.asp" Response.End End If &single; 如果程序运行到这里,说明用户能够查看当前页面 &single; 以下开始创建表单 %> <form method=post action="SomePage.asp"> <input type=submit> </form> 我们借助会话变量FirstTimeToPage检查用户是否是第一次访问当前页面。如果不是第一次(即Session("FirstTimeToPage")包含某个值),那么我们就清除会话变量的值,然后把用户重新定向到一个开始页面。这样,当表单提交时(此时SompePage.asp被打开),我们必须赋予FirstTimeToPage一个值。即,在SomePage.asp中我们需要加上下面的代码: Session("FirstTimeToPage") = "NO" 这样,已经打开SomePage.asp的用户如果点击后退按钮,浏览器将重新请求服务器下载页面,服务器检查到Session("FirstTimeToPage")包含了一个值,于是就清除Session("FirstTimeToPage"),并把用户重定向到其他页面。当然,所有这一切都需要用户启用了Cookie,否则会话变量将是无效的。(有关该问题的更多说明,请参见For session ariables to work, must the Web visitor have cookies enabled?) 另外,我们也可以用客户端代码使浏览器不再缓存Web页面: <html> <head> <meta http-equiv="Expires" CONTENT="0"> <meta http-equiv="Cache-Control" CONTENT="no-cache"> <meta http-equiv="Pragma" CONTENT="no-cache"> </head> 如果使用上面的方法强制浏览器不再缓存Web页面,必须注意以下几点: 只有在使用安全连接时“Pragma: no-cache”才防止浏览器缓存页面。对于不受安全保护的页面,“Pragma: no-cache”被视为与“Expires: -1”相同,此时浏览器仍旧缓存页面,但把页面标记为立即过期。在IE 4或5中,“Cache-Control”META HTTP-EQUIV标记将被忽略,不起作用。 在实际应用中我们可以加上所有这些代码。然而,由于这种方法不能适用于所有的浏览器,所以是不推荐使用的。但如果是在Intranet环境下,管理员可以控制用户使用哪种浏览器,我想还是有人会使用这种方法。 三、其他方法 接下来我们要讨论的方法以后退按钮本身为中心,而不是浏览器缓存。这儿有一篇文章Rewiring the Back Button很值得参考。不过我注意到,如果使用这种方法,虽然用户点击一下后退按钮时他不会看到以前输入数据的页面,但只要点击两次就可以,这可不是我们希望的效果,因为很多时候,固执的用户总是能够找到绕过预防措施的办法。 另外一种禁用后退按钮的办法是用客户端JavaScript打开一个没有工具条的窗口,这使得用户很难返回前一页面,但不是不可能。一种更安全但相当恼人的方法是,当表单提交时打开一个新的窗口,与此同时关闭表单所在的窗口。但我觉得这种方法不值得认真考虑,因为我们总不能让用户每提交一个表单就打开一个新窗口。 那么,在那个我们不想让用户返回的页面是否也可以加入JavaScript代码呢?在这个页面中加入的JavaScript代码可用来产生点击前进按钮的效果,这样也就抵消了用户点击后退按钮所产生的动作。用于实现该功能的JavaScript代码如下 所示: <script language="JavaScript"> <!-- javascript:window.history.forward(1); //--> </script> 同样地,这种方法虽然有效,但距离“最好的方法”还差得很远。后来我又看到有人建议用location.replace从一个页面转到另一个页面。这种方法的原理是,用新页面的URL替换当前的历史纪录,这样浏览历史记录中就只有一个页面,后退按钮永远不会变为可用。我想这可能正是许多人所寻求的方法,但这种方法仍旧不是任何情况下的最好方法。使用这种方法的实例如下所示: <A HREF="PageName.htm" onclick="javascript:location.replace(this.href); event.returnValue=false;">禁止后退到本页面的链接</A> 禁止后退到本页面的链接! 这种方法的缺点在于:简单地运用Response.Redirect将不再有效,这是因为每次用户从一个页面转到另一个页面,我们都必须用客户端代码清除location.history。另外还要注意,这种方法清除的是最后一个访问历史记录,而不是全部的访问记录。 点击上面的链接,你将打开一个简单的HTML页面。再点击后退按钮,你可以看到这时打开的不是本页面,而是本页面之前的页面!(当然,你必须在浏览器中启用了客户端JavaScript代码。) 经过一番仔细的寻寻觅觅之后,我发现仍旧无法找出真正能够完全禁用浏览器后退按钮的办法。所有这里介绍的方法都能够在不同程度上、以不同的方式禁止用户返回前一页面,但它们都有各自的局限。由于不存在能够完全禁用后退按钮的方法,所以最好的方案应该是:混合运用客户端脚本和服务器端脚本。 <html> <head> <meta http-equiv="Expires" CONTENT="0"> <meta http-equiv="Cache-Control" CONTENT="no-cache"> <meta http-equiv="Pragma" CONTENT="no-cache"> </head> <script language="JavaScript"> <!-- javascript:window.history.forward(1); //--> </script> </html> ====第四篇==结束========================================================================== ====第五篇==开始========================================================================== Asp.net中防刷新重复提交、防后退方法 简单操作方法防后退和刷新 Page_Load中加入 Response.Cache.SetNoStore(); //Session中存储的变量“IsSubmit”是标记是否提交成功的 if (!IsPostBack) if (Session["IsSubmit"]==null) Session.Add("IsSubmit",false); if ((bool)Session["IsSubmit"]) { //如果表单数据提交成功,就设“Session["IsSubmit"]”为false Session["IsSubmit"] = false; //显示提交成功信息 TextBox1.Text = " * 提交成功!"; } else {//否则的话(没有提交,或者是页面刷新),不显示任何信息 TextBox1.Text = ""; Response.End(); } 提交按钮中加入 Session["IsSubmit"] = true; Response.Redirect ("本页"); ====第五篇==结束========================================================================== 另外: 1、通常应该在业务层进行判断(唯一性)解决这种问题 2、要在页面装载事件写上 Response.CacheControl = "no-cache" 清除缓存 3、也有人这样说:我以前也碰到过这样的问题,是在分步提交中一个人的简历,在写完第一个页面后跳到第二个页面,为了防止用户用后退返回到第一个页面,再重新提交第一个页面,我是当用户提交第一次提交第一个页面时,把插入数据库中的记录的自增长id号放到session里,当用户从第二个页面返回到第一个页面再一次提交该页面时,我就用session里的值去数据库查,如果有这个id就用update语句把第一个页面的数据写进数据库,如果没有查到这个id,就用insert语句。 Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=2124862 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!394.entry
JS的正则表达式
//校验是否全由数字组成 [code] function isDigit(s) { var patrn=/^[0-9]{1,20}$/; if (!patrn.exec(s)) return false return true } [/code]
//校验登录名:只能输入5-20个以字母开头、可带数字、“_”、“.”的字串 [code] function isRegisterUserName(s) { var patrn=/^[a-zA-Z]{1}([a-zA-Z0-9]|[._]){4,19}$/; if (!patrn.exec(s)) return false return true } [/code]
//校验用户姓名:只能输入1-30个以字母开头的字串 [code] function isTrueName(s) { var patrn=/^[a-zA-Z]{1,30}$/; if (!patrn.exec(s)) return false return true } }} //校验密码:只能输入6-20个字母、数字、下划线 [code] function isPasswd(s) { var patrn=/^(\w){6,20}$/; if (!patrn.exec(s)) return false return true } [/code]
//校验普通电话、传真号码:可以“+”开头,除数字外,可含有“-” [code] function isTel(s) { //var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?(\d){1,12})+$/; var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/; if (!patrn.exec(s)) return false return true } [/code]
//校验手机号码:必须以数字开头,除数字外,可含有“-” [code] function isMobil(s) { var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/; if (!patrn.exec(s)) return false return true } [/code]
//校验邮政编码 [code] function isPostalCode(s) { //var patrn=/^[a-zA-Z0-9]{3,12}$/; var patrn=/^[a-zA-Z0-9 ]{3,12}$/; if (!patrn.exec(s)) return false return true } [/code]
//校验搜索关键字 [code] function isSearch(s) { var patrn=/^[^`~!@#$%^&*()+=|\\\][\]\{\}:;'\,.<>/?]{1}[^`~!@$%^&()+=|\\\] [\]\{\}:;'\,.<>?]{0,19}$/; if (!patrn.exec(s)) return false return true } function isIP(s) //by zergling { var patrn=/^[0-9.]{1,20}$/; if (!patrn.exec(s)) return false return true } [/code]
正则表达式 [code] "^\\d+$" //非负整数(正整数 + 0) "^[0-9]*[1-9][0-9]*$" //正整数 "^((-\\d+)|(0+))$" //非正整数(负整数 + 0) "^-[0-9]*[1-9][0-9]*$" //负整数 "^-?\\d+$" //整数 "^\\d+(\\.\\d+)?$" //非负浮点数(正浮点数 + 0) "^(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*))$" //正浮点数 "^((-\\d+(\\.\\d+)?)|(0+(\\.0+)?))$" //非正浮点数(负浮点数 + 0) "^(-(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*)))$" //负浮点数 "^(-?\\d+)(\\.\\d+)?$" //浮点数 "^[A-Za-z]+$" //由26个英文字母组成的字符串 "^[A-Z]+$" //由26个英文字母的大写组成的字符串 "^[a-z]+$" //由26个英文字母的小写组成的字符串 "^[A-Za-z0-9]+$" //由数字和26个英文字母组成的字符串 "^\\w+$" //由数字、26个英文字母或者下划线组成的字符串 "^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$" //email地址 "^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$" //url "^[A-Za-z0-9_]*$" [/code]
正则表达式使用详解
简介
简单的说,正则表达式是一种可以用于模式匹配和替换的强有力的工具。其作用如下: 测试字符串的某个模式。例如,可以对一个输入字符串进行测试,看在该字符串是否存在一个电话号码模式或一个信用卡号码模式。这称为数据有效性验证。 替换文本。可以在文档中使用一个正则表达式来标识特定文字,然后可以全部将其删除,或者替换为别的文字。 根据模式匹配从字符串中提取一个子字符串。可以用来在文本或输入字段中查找特定文字。
基本语法
在对正则表达式的功能和作用有了初步的了解之后,我们就来具体看一下正则表达式的语法格式。
正则表达式的形式一般如下:
/love/ 其中位于“/”定界符之间的部分就是将要在目标对象中进行匹配的模式。用户只要把希望查找匹配对象的模式内容放入“/”定界符之间即可。为了能够使用户更加灵活的定制模式内容,正则表达式提供了专门的“元字符”。所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符,可以用来规定其前导字符(即位于元字符前面的字符)在目标对象中的出现模式。 较为常用的元字符包括: “+”, “*”,以及 “?”。
“+”元字符规定其前导字符必须在目标对象中连续出现一次或多次。
“*”元字符规定其前导字符必须在目标对象中出现零次或连续多次。
“?”元字符规定其前导对象必须在目标对象中连续出现零次或一次。
下面,就让我们来看一下正则表达式元字符的具体应用。
/fo+/ 因为上述正则表达式中包含“+”元字符,表示可以与目标对象中的 “fool”, “fo”, 或者 “football”等在字母f后面连续出现一个或多个字母o的字符串相匹配。
/eg*/ 因为上述正则表达式中包含“*”元字符,表示可以与目标对象中的 “easy”, “ego”, 或者 “egg”等在字母e后面连续出现零个或多个字母g的字符串相匹配。
/Wil?/ 因为上述正则表达式中包含“?”元字符,表示可以与目标对象中的 “Win”, 或者“Wilson”,等在字母i后面连续出现零个或一个字母l的字符串相匹配。
有时候不知道要匹配多少字符。为了能适应这种不确定性,正则表达式支持限定符的概念。这些限定符可以指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。
{n} n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
{n,} n 是一个非负整数。至少匹配 n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。
除了元字符之外,用户还可以精确指定模式在匹配对象中出现的频率。例如,/jim {2,6}/ 上述正则表达式规定字符m可以在匹配对象中连续出现2-6次,因此,上述正则表达式可以同jimmy或jimmmmmy等字符串相匹配。 在对如何使用正则表达式有了初步了解之后,我们来看一下其它几个重要的元字符的使用方式。 [code] \s:用于匹配单个空格符,包括tab键和换行符; \S:用于匹配除单个空格符之外的所有字符; \d:用于匹配从0到9的数字; \w:用于匹配字母,数字或下划线字符; \W:用于匹配所有与\w不匹配的字符; . :用于匹配除换行符之外的所有字符。 [/code] (说明:我们可以把\s和\S以及\w和\W看作互为逆运算) 下面,我们就通过实例看一下如何在正则表达式中使用上述元字符。 /\s+/ 上述正则表达式可以用于匹配目标对象中的一个或多个空格字符。 /\d000/ 如果我们手中有一份复杂的财务报表,那么我们可以通过上述正则表达式轻而易举的查找到所有总额达千元的款项。 除了我们以上所介绍的元字符之外,正则表达式中还具有另外一种较为独特的专用字符,即定位符。定位符用于规定匹配模式在目标对象中的出现位置。 较为常用的定位符包括: “^”, “$”, “\b” 以及 “\B”。 [code] “^”定位符规定匹配模式必须出现在目标字符串的开头 “$”定位符规定匹配模式必须出现在目标对象的结尾 “\b”定位符规定匹配模式必须出现在目标字符串的开头或结尾的两个边界之一 “\B”定位符则规定匹配对象必须位于目标字符串的开头和结尾两个边界之内, 即匹配对象既不能作为目标字符串的开头,也不能作为目标字符串的结尾。 [/code] 同样,我们也可以把“^”和“$”以及“\b”和“\B”看作是互为逆运算的两组定位符。举例来说: /^hell/ 因为上述正则表达式中包含“^”定位符,所以可以与目标对象中以 “hell”, “hello”或“hellhound”开头的字符串相匹配。 /ar$/ 因为上述正则表达式中包含“$”定位符,所以可以与目标对象中以 “car”, “bar”或 “ar” 结尾的字符串相匹配。 /\bbom/ 因为上述正则表达式模式以“\b”定位符开头,所以可以与目标对象中以 “bomb”, 或 “bom”开头的字符串相匹配。/man\b/ 因为上述正则表达式模式以“\b”定位符结尾,所以可以与目标对象中以 “human”, “woman”或 “man”结尾的字符串相匹配。 为了能够方便用户更加灵活的设定匹配模式,正则表达式允许使用者在匹配模式中指定某一个范围而不局限于具体的字符。例如: [code] /[A-Z]/ 上述正则表达式将会与从A到Z范围内任何一个大写字母相匹配。 /[a-z]/ 上述正则表达式将会与从a到z范围内任何一个小写字母相匹配。 /[0-9]/ 上述正则表达式将会与从0到9范围内任何一个数字相匹配。 /([a-z][A-Z][0-9])+/ 上述正则表达式将会与任何由字母和数字组成的字符串,如 “aB0” 等相匹配。 [/code] 这里需要提醒用户注意的一点就是可以在正则表达式中使用 “()” 把字符串组合在一起。“()”符号包含的内容必须同时出现在目标对象中。因此,上述正则表达式将无法与诸如 “abc”等的字符串匹配,因为“abc”中的最后一个字符为字母而非数字。 如果我们希望在正则表达式中实现类似编程逻辑中的“或”运算,在多个不同的模式中任选一个进行匹配的话,可以使用管道符 “|”。例如:/to|too|2/ 上述正则表达式将会与目标对象中的 “to”, “too”, 或 “2” 相匹配。 正则表达式中还有一个较为常用的运算符,即否定符 “[^]”。与我们前文所介绍的定位符 “^” 不同,否定符 “[^]”规定目标对象中不能存在模式中所规定的字符串。例如:/[^A-C]/ 上述字符串将会与目标对象中除A,B,和C之外的任何字符相匹配。一般来说,当“^”出现在 “[]”内时就被视做否定运算符;而当“^”位于“[]”之外,或没有“[]”时,则应当被视做定位符。 最后,当用户需要在正则表达式的模式中加入元字符,并查找其匹配对象时,可以使用转义符“\”。例如:/Th\*/ 上述正则表达式将会与目标对象中的“Th*”而非“The”等相匹配。 在构造正则表达式之后,就可以象数学表达式一样来求值,也就是说,可以从左至右并按照一个优先级顺序来求值。优先级如下: [code] 1.\ 转义符 2.(), (?:), (?=), [] 圆括号和方括号 3.*, +, ?, {n}, {n,}, {n,m} 限定符 4.^, $, \anymetacharacter 位置和顺序 5.|“或”操作 [/code]
使用实例 在JavaScript 1.2中带有一个功能强大的RegExp()对象,可以用来进行正则表达式的匹配操作。其中的test()方法可以检验目标对象中是否包含匹配模式,并相应的返回true或false。 我们可以使用JavaScript编写以下脚本,验证用户输入的邮件地址的有效性。 [code] <html> <head> <script language="Javascript1.2"> <!-- start hiding function verifyAddress(obj) { var email = obj.email.value; var pattern = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/; flag = pattern.test(email); if(flag) { alert(“Your email address is correct!”); return true; } else { alert(“Please try again!”); return false; } } // stop hiding --> </script> </head> <body> <form onSubmit="return verifyAddress(this);"> <input name="email" type="text"> <input type="submit"> </form> </body> </html> [/code]
正则表达式对象 本对象包含正则表达式模式以及表明如何应用模式的标志。 [code] 语法 1 re = /pattern/[flags] 语法 2 re = new RegExp("pattern",["flags"]) [/code] 参数 re 必选项。将要赋值为正则表达式模式的变量名。
Pattern 必选项。要使用的正则表达式模式。如果使用语法 1,用 "/" 字符分隔模式。如果用语法 2,用引号将模式引起来。
Flags 可选项。如果使用语法 2 要用引号将 flag 引起来。标志可以组合使用,可用的有: [code] g (全文查找出现的所有 pattern) i (忽略大小写) m (多行查找) [/code]
示例 下面的示例创建一个包含正则表达式模式及相关标志的对象(re),向您演示正则表达式对象的用法。在本例中,作为结果的正则表达式对象又用于 match 方法中: [code] function MatchDemo() { var r, re; // 声明变量。 var s = "The rain in Spain falls mainly in the plain"; re = new RegExp("ain","g"); // 创建正则表达式对象。 r = s.match(re); // 在字符串 s 中查找匹配。 return(r); } [/code]
返回值: ain,ain,ain,ain\\ 属性 lastIndex 属性 | source 属性\\ 方法 compile 方法 | exec 方法 | test 方法\\ 要求 版本 3\\ 请参阅 RegExp 对象 | 正则表达式语法 | String 对象\\
exec 方法 用正则表达式模式在字符串中运行查找,并返回包含该查找结果的一个数组。 rgExp.exec(str)
参数
rgExp 必选项。包含正则表达式模式和可用标志的正则表达式对象。
str 必选项。要在其中执行查找的 String 对象或字符串文字。
说明\\ 如果 exec 方法没有找到匹配,则它返回 null。如果它找到匹配,则 exec 方法返回一个数组,并且更新全局 RegExp 对象的属性,以反映匹配结果。数组的0元素包含了完整的匹配,而第1到n元素中包含的是匹配中出现的任意一个子匹配。这相当于没有设置全局标志 (g) 的 match 方法。 如果为正则表达式设置了全局标志,exec 从以 lastIndex 的值指示的位置开始查找。如果没有设置全局标志,exec 忽略 lastIndex 的值,从字符串的起始位置开始搜索。
exec 方法返回的数组有三个属性,分别是 input、index 和 lastIndex。Input 属性包含了整个被查找的字符串。Index 属性中包含了整个被查找字符串中被匹配的子字符串的位置。LastIndex 属性中包含了匹配中最后一个字符的下一个位置。
示例\\ 下面的例子举例说明了 exec 方法的用法: [code] function RegExpTest() { var ver = Number(ScriptEngineMajorVersion() + "." + ScriptEngineMinorVersion()) if (ver >= 5.5){ // 测试 JScript 的版本。 var src = "The rain in Spain falls mainly in the plain."; var re = /\w+/g; // 创建正则表达式模式。 var arr; while ((arr = re.exec(src)) != null) document.write(arr.index + "-" + arr.lastIndex + arr + "\t"); } else{ alert("请使用 JScript 的更新版本"); } } [/code]
返回值:0-3The 4-8rain 9-11in 12-17Spain 18-23falls 24-30mainly 31-33in 34-37the 38-43plain
test 方法\\ 返回一个 Boolean 值,它指出在被查找的字符串中是否存在模式。 rgexp.test(str)
参数\\ rgexp 必选项。包含正则表达式模式或可用标志的正则表达式对象。
str 必选项。要在其上测试查找的字符串。
说明 test 方法检查在字符串中是否存在一个模式,如果存在则返回 true,否则就返回 false。 全局 RegExp 对象的属性不由 test 方法来修改。
示例 下面的例子举例说明了 test 方法的用法: [code] function TestDemo(re, s) { var s1; // 声明变量。 // 检查字符串是否存在正则表达式。 if (re.test(s)) // 测试是否存在。 s1 = " contains "; // s 包含模式。 else s1 = " does not contain "; // s 不包含模式。 return("'" + s + "'" + s1 + "'"+ re.source + "'"); // 返回字符串。 } [/code]
函数调用:document.write (TestDemo(/ain+/ ,"The rain in Spain falls mainly in the plain."));
返回值:'The rain in Spain falls mainly in the plain.' contains 'ain+'
match 方法 使用正则表达式模式对字符串执行查找,并将包含查找的结果作为数组返回。\\ stringObj.match(rgExp)
参数\\ stringObj 必选项。对其进行查找的 String 对象或字符串文字。
rgExp 必选项。为包含正则表达式模式和可用标志的正则表达式对象。也可以是包含正则表达式模式和可用标志的变量名或字符串文字。
说明\\ 如果 match 方法没有找到匹配,返回 null。如果找到匹配返回一个数组并且更新全局 RegExp 对象的属性以反映匹配结果。 match 方法返回的数组有三个属性:input、index 和 lastIndex。Input 属性包含整个的被查找字符串。Index 属性包含了在整个被查找字符串中匹配的子字符串的位置。LastIndex 属性包含了最后一次匹配中最后一个字符的下一个位置。 如果没有设置全局标志 (g),数组的 0 元素包含整个匹配,而第 1 到 n 元素包含了匹配中曾出现过的任一个子匹配。这相当于没有设置全局标志的 exec 方法。如果设置了全局标志,元素 0 到 n 中包含所有匹配。
示例\\ 下面的示例演示了match 方法的用法: [code] function MatchDemo() { var r, re; // 声明变量。 var s = "The rain in Spain falls mainly in the plain"; re = /ain/i; // 创建正则表达式模式。 r = s.match(re); // 尝试匹配搜索字符串。 return(r); // 返回第一次出现 "ain" 的地方。 } [/code] 返回值:ain
本示例说明带 g 标志设置的 match 方法的用法。 [code] function MatchDemo() { var r, re; // 声明变量。 var s = "The rain in Spain falls mainly in the plain"; re = /ain/ig; // 创建正则表达式模式。 r = s.match(re); // 尝试去匹配搜索字符串。 return(r); // 返回的数组包含了所有 "ain" // 出现的四个匹配。 } [/code] 返回值:ain,ain,ain,ain
上面几行代码演示了字符串文字的 match 方法的用法。 [code] var r, re = "Spain"; r = "The rain in Spain".replace(re, "Canada"); return r; [/code] 返回值:The rain in Canada
search 方法 返回与正则表达式查找内容匹配的第一个子字符串的位置。
stringObj.search(rgExp)
参数\\ stringObj 必选项。要在其上进行查找的 String 对象或字符串文字。
rgExp 必选项。包含正则表达式模式和可用标志的正则表达式对象。
说明
search 方法指明是否存在相应的匹配。如果找到一个匹配,search 方法将返回一个整数值,指明这个匹配距离字符串开始的偏移位置。如果没有找到匹配,则返回 -1。
示例\\ 下面的示例演示了 search 方法的用法。 [code] function SearchDemo() { var r, re; // 声明变量。 var s = "The rain in Spain falls mainly in the plain."; re = /falls/i; // 创建正则表达式模式。 r = s.search(re); // 查找字符串。 return(r); // 返回 Boolean 结果。 } [/code] 返回值:18
Replace方法 返回根据正则表达式进行文字替换后的字符串的复制。 stringObj.replace(rgExp, replaceText) 参数- stringObj
- 必选项。要执行该替换的 String 对象或字符串文字。该字符串不会被 replace 方法修改。
- rgExp
- 必选项。为包含正则表达式模式或可用标志的正则表达式对象。也可以是 String 对象或文字。
- 如果 rgExp 不是正则表达式对象,它将被转换为字符串,并进行精确的查找;
- 不要尝试将字符串转化为正则表达式。
- replaceText
- 必选项。是一个String 对象或字符串文字,对于stringObj 中每个匹配 rgExp 中的位置都用
- 该对象所包含的文字加以替换。在 Jscript 5.5 或更新版本中,replaceText 参数也可以
- 是返回替换文本的函数。
说明 replace 方法的结果是一个完成了指定替换的 stringObj 对象的复制。 下面任意的匹配变量都能用来识别最新的匹配以及找出匹配的字符串。在需要动态决定替换字符串 的文本替换中可以使用匹配变量。 字符 | 含义 |
---|
$$ | $ (JScript 5.5 或更新版本) | $& | 指定与整个模式匹配的 stringObj 的部分。(JScript 5.5 或更新版本) | $` | 指定由 $& 描述的匹配之前的 stringObj 部分。(JScript 5.5 或更新版本) | $' | 指定由 $& 描述的匹配之后的 stringObj 部分。(JScript 5.5 或更新版本) | $n | 捕获的第 n 个子匹配,此处 n 为从1到9的十进制一位数。(JScript 5.5 或更新版本) | $nn | 捕获的第 nn 个子匹配,此处 nn 为从01到99的十进制两位数。(JScript 5.5 或更新版本) |
如果 replaceText 为函数,对于每一个匹配的子字符串,调用该函数时带有下面的 m+3 个 参数,此处 m 是在 rgExp 中捕获的左括弧的个数。第一个参数是匹配的子字符串。 接下来的 m 个参数是查找中捕获的全部结果。第 m+2 个参数是在 stringObj 中匹配出现的偏移量, 而第 m+3 个参数为 stringObj。结果为将每一匹配的子字符串替换为函数调用的相应返回值的 字符串值。 replace() 方法的参数 replacement 可以是函数而不是字符串。在这种情况下, 每个匹配都调用该函数,它返回的字符串将作为替换文本使用。该函数的第一个参数是匹配模式 的字符串。接下来的参数是与模式中的子表达式匹配的字符串,可以有 0 个或多个这样的 参数。接下来的参数是一个整数,声明了匹配在 stringObject 中出现的位置。 最后一个参数是 stringObject 本身。 更有效的字符串替换: <script language="javascript"> function replace(str,a,b) { var i; var s2 = str; while(s2.indexOf(a)>0) { i = s2.indexOf(a); s2 = s2.substring(0, i) + b + s2.substring(i + 2, s2.length); } return s2; } </script> 发现一个我认为比原来的方法更有效率的字符串替换方法。 首先看看原来是怎么样做的: 程序代码 function toTXT(str){ str = str.replace(/\&/g, "& amp;"); str = str.replace(/\>/g, "& gt;"); str = str.replace(/\</g, "& lt;"); str = str.replace(/\"/g, "& quot;"); str = str.replace(/\'/g, "& #39;"); return str; } 分析:上面的方法是用来替换过滤字符串的HTML代码的,一直认为这样做很没效率, 因为要把这个字符串进行5次全文匹配(不是数据库中的全文检索),有没方法只使 用一次全文匹配,就可以替换掉不同的字符串为不同的结果呢? 呵呵,终于找到了下面的这个方法: 程序代码 function toTXT(str){ var RexStr = /\<|\>|\"|\'|\&/g str = str.replace(RexStr, function(MatchStr){ switch(MatchStr){ case "<": return "& lt;"; break; case ">": return "& gt;"; break; case "\"": return "& quot;"; break; case "'": return "& #39;"; break; case "&": return "& amp;"; break; default : break; } } ) return str; } 看上去要比第一个方法要复杂一些,写的代码也要多一点点,不过呢,只用一次, 就可以替换整个字符串里面的匹配字符为不同的结果,非常有效率的。而且代码看 上去也很直观,修改方便。 更重要的是第一个方法假如要替换"&"一定要放在最前面,而后面的方面完全不用担心这个问题。 JavaScript还有很多鲜为人知的用法,而且很多思想在别的语言里面都没有。
就算JAVA再怎么强大,他的正则表达式却在JDK1.4里面才引入,落后了很多。 不过我没说JAVA就一定比JS差只类的话啊。 JS的应用范围绝对不止HTML,还有很多其他的方面都在使用他,
比如WebFT(测试网站的工具),.NET等,并且在即将发布,传说中“FLASH杀手” ——WPF/E(Windows Presentation Foundation/Everywhere)出现后, JS的应用范围更加宽广。 很多人都不屑于JS,认为他很低级,连基本的对象类型都没有。NO,错了,在.NET里面,
也就是Jscript.Net中,微软将JS的版本提升为8.0,基本上他已经与C#没有任何差别, 所有程序语言该有的他都具备了。 说一个笑话,我写.net和ASP就用的Jscript,但是VB和C#我也并不是属于那种泛泛而谈
的类型,只是我觉得假如可以用一门语言从后写到前,包括他的周边项目,那将是一件 非常惬意的事情。从此再也不会在多种语言中转来转去了。 Replace 方法更新全局 RegExp 对象的属性。 示例下面的示例演示了 replace 方法将第一次出现的单词 "The" 替换为单词 "A" 的用法。 function ReplaceDemo(){
var r, re; // 声明变量。
var ss = "The man hit the ball with the bat.\n";
ss += "while the fielder caught the ball with the glove.";
re = /The/g; // 创建正则表达式模式。
r = ss.replace( re, "A") ; // 用 "A" 替换 "The"。
return(r); // 返回替换后的字符串。
} 另外, replace 方法也可以替换模式中的子表达式。下面的范例演示了交换字符串中的每一对单词: function ReplaceDemo(){
var r, re; // 声明变量。
var ss = "The rain in Spain falls mainly in the plain.";
re = /(\S+)(\s+)(\S+)/g; // 创建正则表达式模式。
r = ss.replace( re, "$3$2$1") ; // 交换每一对单词。
return(r); // 返回结果字符串。
} 下面的示例(在 JScript 5.5 及更新版本中执行)执行的是从华氏到摄氏的转换,它演示了使用 函数作为 replaceText。要想知道该函数是如何工作的,传递一个包含数值的字符串,数值后要 紧跟 "F" (例如 "Water boils at 212")。 function f2c(s) {
var test = /(\d+(\.\d*)?)F\b/g; // 初始化模式。
return(s.replace
(test,
function($0,$1,$2) {
return((($1-32) * 5/9) + "C");
}
)
);
}
document.write(f2c("Water freezes at 32F and boils at 212F."));
function($0,$1,$2) {
return((($1-32) * 5/9) + "C");
}
)
);
}
document.write(f2c("Water freezes at 32F and boils at 212F.")); 自己写的两个方法: <html> <SCRIPT LANGUAGE="JavaScript"> <!-- function replaceAll(str,b){ //var renStr=str.replace("-","*");这样写只能替换第一个匹配的字符. var renStr=str.replace(/\-/g,b);//这样写可以替换匹配的字符所有字符. 与replaceAllNew()方法作用是一样的. document.all.text4.value=renStr; document.all.text2.value=""; document.all.text3.value=""; } function replaceAllNew(text,replacement,target){ if(text==null || text=="") return text;//如果text无内容,返回text if(replacement==null || replacement=="") return text;//如果replacement无内容,返回text if(target==null) target="";//如果target无内容,设置为空串 var returnString="";//定义返回值变量,并初始化 var index=text.indexOf(replacement);//定义查找replacement的索引下标,并进行第一次查找 while(index!=-1) {//直至未找到replacement,要么进行下面的处理 //alert(index); //alert(returnString); returnString+=text.substring(0,index)+target;//如果找到的replacement前有字符,
累加到返回值中,并加上target text=text.substring(index+replacement.length);//取掉找到的replacement及前边的字符 index=text.indexOf(replacement);//进行查询,准备下一次处理 } if(text!="") returnString+=text;//如果找到的最后一个replacement后有字符,累加到返回值中 //alert(returnString); document.all.text4.value=returnString; document.all.text2.value=""; document.all.text3.value=""; return returnString;//返回 } function bodyOnload(){ var str1="adbddasd"; //replaceAll(str1,'a','A'); } //--> </SCRIPT> <body > 1:javascript 中的replace方法测试!<br> 原来的:<input type="text" value="">;转换后的:<input type="text" value=""><br> 把字符:<input type="text" value=""><br> 替换为:<input type="text" value=""><br> <input type="button" value="replace1" /> <input type="button" value="replace2" /> </body> </html>
文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!393.entry
Struts controller基本功能是: 1. 截获用户的Http请求 2. 把这个请求映射到相应的Action类,如果这是此类收到的第一个请求,将初始化实例并缓存。 3. 创建或发现一个ActionForm bean实例(看配置文件是否定义),然后将请求过程移植到bean,填充所需的各个参数。 4. 调用Action实例的perform()方法并将ActioForm bean,Action Mapping对象,request和response对象传给它。 如:public ActionForword perform(ActionMapping mapping, ActionForm form,HttpServletRequest request,HttpServletResponse response) 5.perform返回一个ActionForword对象,此对象连接到相应的jsp 。 6. ActionForward 出来后,还是交给 ActionServlet ,form / request 还在。只要没有 response ,request 还可以由 ActionServlet 转交给别的 aciton 而做别的事情。 ActionServlet使用ActionForm bean来保存请求的参数,这些bean的属性名称与HTTP请求参数的名称相对应,控制器将请求参数传递到ActionForm bean的实例,然后将这个实例传送到Action类。 典型的ActionFrom bean只有属性的设置与读取方法(getXXX),而没有实现事务逻辑的方法。只有简单的输入检查逻辑,使用的目的是为了存储用户在相关表单中输入的最新数据,以便可以将同一网页进行再生,同时提供一组错误信息,这样就可以让用户修改不正确的输入数据。而真正对数据有效性进行检查的是ACTION类或适当的事务逻辑bean。 有几个部分共同组成了Struts 的Controller,用户的请求发送到ActionServlet中,ActionServlet调用RequestProssor开始处理用户请求的流程,在这个流程中,会查找ApplicationConfig,得到用户请求对应的Action,调用相应的Action来具体执行用户的请求,最后返回ActionForward,转向相应的流程。 org.apache.struts.action.ActionServlet 是Struts Controller中最主要的部分,所有用户请求都会被发送到这里,所有的其它处理也必须从这里经过。它是从 HttpServlet中继承过来的, 当它接收到HTTP request的时候,不管是doGet()或者doPost()方法,都会调用process()方法。 protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { RequestUtils.selectApplication( request, getServletContext() ); getApplicationConfig(request).getProcessor().process( request, response ); } 一般情况下,我们不需要自己实现或者修改ActionServlet类,仅仅使用就可以了。某些情况下,我们可以自己扩展 ActionServlet类,从ActionServlet继承,实现自己的MyActionServlet类。覆盖其中的一些方法来达到你的特殊处理的需要。 ActionServlet继承自javax.servlet.http.HttpServlet,所以在本质上它和一个普通的servlet没有区别,你完全可以把它当做一个servlet来看待,只是在其中完成的功能不同罢了。 RequestProssor具体处理用户的request,作为一个request handler存在。同样,处理request的时候,会执行RequestProcessor类中的process(execute)方法。 process 中调用的方法都是可以重载的,如果有需要,可以实现为自己特定的方法。比如,对于Locale问题,通常都是在系统最一开始加载的时候读取的,如果用户想在任何时刻都可以切换或者选择自己的Locale,我们就可以重载processLocale()方法。然后只需要在配置文件中加入段就可以了 Action 类是实现整个体系的核心部分,它在客户请求、界面表示和业务逻辑之间起到一个桥梁的作用。每一个Action都用来处理某一项任务,或者进行一个业务操作。当然了,我们说一项任务不是说Action只实现一个业务操作方法,而是集中实现某一个功能单元。比如登录用的LogonAction、查找用的 SearchAction等等。Action是在RequestProcessor中,由processActionPerform方法调用的 非常重要的一点:不要在Action中包含任何业务逻辑操作,而是应该调用一个Model层的JavaBean来实现你的业务逻辑操作。在某些情况下,可能包含少许表现逻辑。这样,就可以充分进行代码重用,比如上例中调用的IStorefrontService接口,这个接口在实现时完全可以不用考虑客户端的事情,所以它可以被其它部分或者其它系统所使用。否则的话,Action会变得非常难于理解,难于维护,代码也不能重用。 struts-example工程的设计就是一个bug,它把业务逻辑封装到了Action类中 在Action 的execute方法中,返回一个ActionForward类。 ActionForward把配置文件中forward部分的信息包装起来,减少了应用程序和物理资源信息之间的耦合性。通过ActionMapping类,可以在配置文件中查找相应的forward信息。例如,对于一个 LoginAction它的配置信息可能是这样的: 返回的ActionForward就会包含段中的信息。在ActionMapping类的findForward方法中,首先会根据查找forward的name查找是否有相应的forward段,如果没有,则在配置文件中的段中进行查找,如果还没有就会抛出一个例外。 以前,页面上的输入数据都通过HTTP request提交,服务方检索出输入的数据,进行验证,然后将这些数据传递给其它组件进行业务处理。一切基本都需要手工编写代码进行操作,比较麻烦,也使代码变得复杂。 ActionForm[org.apache.struts.action.ActionForm]用来收集用户的输入,并且把这些信息传递给Action对象,然后,在Action对象中,ActionForm中的数据被取出来传递给业务逻辑层进行处理。 ActionForm一方面作为一个缓冲区,临时存储用户输入的数据;另一方面,可以把ActionForm当成是HTTP和Action之间的一个防火墙,它可以验证输入数据的正确性,如果验证不通过,这个request是不会发送给Action进行处理的。 ActionForm可以有两种Scope,request或者session。request就是只能在rquest到response,之后ActionForm就不可见了;session可以保存时间长一点。 在ActionForm的Validate方法中返回的是ActionErrors对象。这个对象可以将错误信息都封装起来,并且自动把它们显示给用户。 在相应JSP页面上添加,可以自动将ActionErrors中的错误信息显示出来。包括,每一个具体的,通过add添加的错误信息,和一个ErrorHeader和一个ErrorFooter,这些都可以通过配置文件指定,并且可以包含HTML语法。 Struts提供了四种自定义Tag库: bean:struts-bean taglib包含在访问bean和bean属性时使用的tag,也包含一些消息显示的tag。 html:struts-html taglib包含用来创建struts输入表单的tag,和其它通常用来创建基于HTML用户界面的tag。 logic:struts-logic taglib包含的tag用来管理根据条件生成输出文本,和其它一些用来控制的信息。 template:struts-template taglib包含的tag用来定义模板机制。 以下是一个范例: reguser.jsp: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="/WEB-INF/Struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/Struts-html.tld" prefix="html" %> <html:html locale="true"> <head> <title>RegUser</title> <html:base/> </head> <body bgcolor="white"> <html:errors/> <html:form action="/regUserAction" focus="logname"> <table border="0" width="100%"> <tr> <th align="right"> Logname: </th> <td align="left"> <html:text property="logname" size="20" maxlength="20"/> </td> </tr> <tr> <th align="right"> Password: </th> <td align="left"> <html:password property="password" size="20" maxlength="20"/> </td> </tr> <tr> <th align="right"> E-mail: </th> <td align="left"> <html:password property="email" size="30" maxlength="50"/> </td> </tr> <tr> struts-config.xml: <Struts-config> <form-beans> <form-bean name="regUserForm" type="org.cjea.Struts.example. RegUserForm "/> </form-beans> <action-mappings> <action path="/regUserAction" type=" org.cjea.Struts.example.RegUserAction " attribute=" regUserForm " scope="request" validate="false"> <forward name="failure" path="/ messageFailure.jsp"/> <forward name="success" path="/ messageSuccess.jsp"/> </action> </action-mappings> </Struts-config> RegUserForm: import javax.Servlet.http.HttpServletRequest; import org.apache.Struts.action.ActionForm; import org.apache.Struts.action.ActionMapping; public final class RegUserForm extends ActionForm{ private String logname; private String password; private String email; public RegUserForm(){ logname = null; password = null; email = null; } public String getLogName() { return this.logname; } public void setLogName(String logname) { this.logname = logname; } public void setPassWord(String password) { this.password = password; } public String getPassWord() { return this.password; } public void setEmail(String email) { this.email = email; } public String getEmail() { return this.email; } public void reset(ActionMapping mapping, HttpServletRequest request) { logname = null; password = null; email = null; } } RegUserAction : import javax.Servlet.http.*; import org.apache.Struts.action.*; public final class RegUserAction extends Action { public ActionForward perform(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) { String title = req.getParameter("title"); String password = req.getParameter("password"); String email = req.getParameter("email"); } } 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!373.entry
之前我介绍过一种按位数编码保存树形结构数据的表设计方法,详情见: 浅谈数据库设计技巧(上) 该设计方案的优点是:只用一条查询语句即可得到某个根节点及其所有子孙节点的先序遍历。由于消除了递归,在数据记录量较大时,可以大大提高列表效率。但是,这种编码方案由于层信息位数的限制,限制了每层能所允许的最大子节点数量及最大层数。同时,在添加新节点的时候必须先计算新节点的位置是否超过最大限制。 上面的设计方案必须预先设定类别树的最大层数以及最大子节点数,不是无限分级,在某些场合并不能采用,那么还有更完美的解决方案吗?通过 google的搜索,我又探索到一种全新的无递归查询,无限分级的编码方案——左右值。原文的程序代码是用php写的,但是通过仔细阅读其数据库表设计说明及相关的sql语句,我彻底弄懂了这种巧妙的设计思路,并在这种设计中新增了删除节点,同层平移的需求(原文只提供了列表及插入子节点的sql语句)。 下面我力图用比较简短的文字,少量图表,及相关核心sql语句来描述这种设计方案: 首先,我们弄一棵树作为例子: 商品 |---食品 | |---肉类 | | |--猪肉 | |---蔬菜类 | |--白菜 |---电器 |--电视机 |--电冰箱 采用左右值编码的保存该树的数据记录如下(设表名为tree): Type_id | Name | Lft | Rgt | 1 | 商品 | 1 | 18 | 2 | 食品 | 2 | 11 | 3 | 肉类 | 3 | 6 | 4 | 猪肉 | 4 | 5 | 5 | 蔬菜类 | 7 | 10 | 6 | 白菜 | 8 | 9 | 7 | 电器 | 12 | 17 | 8 | 电视机 | 13 | 14 | 9 | 电冰箱 | 15 | 16 | 第一次看见上面的数据记录,相信大部分人都不清楚左值(Lft)和右值(Rgt)是根据什么规则计算出来的,而且,这种表设计似乎没有保存父节点的信息。下面把左右值和树结合起来,请看: 1商品18 +---------------------------------------+ 2食品11 12电器17 +-----------------+ +---------------------+ 3肉类6 7蔬菜类10 13电视机14 15电冰箱16 4猪肉5 8白菜9 请用手指指着上图中的数字,从1数到18,学习过数据结构的朋友肯定会发现什么吧?对,你手指移动的顺序就是对这棵树的进行先序遍历的顺序。接下来,让我讲述一下如何利用节点的左右值,得到该节点的父节点,子孙节点数量,及自己在树中的层数。 假定我们要对节点“食品”及其子孙节点进行先序遍历的列表,只需使用如下一条sql语句: select * from tree where Lft between 2 and 11 order by Lft asc 查询结果如下: Type_id | Name | Lft | Rgt | 2 | 食品 | 2 | 11 | 3 | 肉类 | 3 | 6 | 4 | 猪肉 | 4 | 5 | 5 | 蔬菜类 | 7 | 10 | 6 | 白菜 | 8 | 9 | 那么某个节点到底有多少子孙节点呢?很简单,子孙总数 =(右值-左值-1)/2 以节点“食品”举例,其子孙总数=(11-2-1)/ 2 = 4 同时,我们在列表显示整个类别树的时候,为了方便用户直观的看到树的层次,一般会根据节点所处的层数来进行相应的缩进,那么,如何计算节点在树中的层数呢?还是只需通过左右值的查询即可,以节点“食品”举例,sql语句如下: select count(*) from tree where lft <= 2 and rgt >= 11 然后,我们建立如下视图: CREATE VIEW dbo.TreeView AS SELECT type_id, name, lft, rgt, dbo.CountLayer(type_id) AS layer FROM dbo.tree ORDER BY lft GO 给出对于给定某个节点,对该节点及其子孙节点进行先序遍历的存储过程: CREATE PROCEDURE [dbo].[GetTreeListByNode] ( @type_id int --给定节点标识 ) AS declare @lft int declare @rgt int if exists (select 1 from tree where type_id=@type_id) begin select @lft=lft,@rgt=rgt from tree where type_id=@type_id select * from TreeView where lft between @lft and @rgt order by lft asc end go
现在,我们使用上面的存储过程来列表节点“食品”及其所有子孙节点,查询结果如下: Type_id | Name | Lft | Rgt | Layer | 2 | 食品 | 2 | 11 | 2 | 3 | 肉类 | 3 | 6 | 3 | 4 | 猪肉 | 4 | 5 | 4 | 5 | 蔬菜类 | 7 | 10 | 3 | 6 | 白菜 | 8 | 9 | 4 | 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!370.entry
说到数据库,我认为不能不先谈数据结构。1996年,在我初入大学学习计算机编程时,当时的老师就告诉我们说:计算机程序=数据结构+算法。尽管现在的程序开发已由面向过程为主逐步过渡到面向对象为主,但我还是深深赞同8年前老师的告诉我们的公式:计算机程序=数据结构+算法。面向对象的程序开发,要做的第一件事就是,先分析整个程序中需处理的数据,从中提取出抽象模板,以这个抽象模板设计类,再在其中逐步添加处理其数据的函数(即算法),最后,再给类中的数据成员和函数划分访问权限,从而实现封装。 数据库的最初雏形据说源自美国一个奶牛场的记账薄(纸质的,由此可见,数据库并不一定是存储在电脑里的数据^_^),里面记录的是该奶牛场的收支账目,程序员在将其整理、录入到电脑中时从中受到启发。当按照规定好的数据结构所采集到的数据量大到一定程度后,出于程序执行效率的考虑,程序员将其中的检索、更新维护等功能分离出来,做成单独调用的模块,这个模块后来就慢慢发展、演变成现在我们所接触到的数据库管理系统(DBMS)——程序开发中的一个重要分支。 下面进入正题,首先按我个人所接触过的程序给数据库设计人员的功底分一下类: 1、没有系统学习过数据结构的程序员。这类程序员的作品往往只是他们的即兴玩具,他们往往习惯只设计有限的几个表,实现某类功能的数据全部塞在一个表中,各表之间几乎毫无关联。网上不少的免费管理软件都是这样的东西,当程序功能有限,数据量不多的时候,其程序运行起来没有什么问题,但是如果用其管理比较重要的数据,风险性非常大。 2、系统学习过数据结构,但是还没有开发过对程序效率要求比较高的管理软件的程序员。这类人多半刚从学校毕业不久,他们在设计数据库表结构时,严格按照教科书上的规定,死扣E-R图和3NF(别灰心,所有的数据库设计高手都是从这一步开始的)。他们的作品,对于一般的access型轻量级的管理软件,已经够用。但是一旦该系统需要添加新功能,原有的数据库表差不多得进行大换血。 3、第二类程序员,在经历过数次程序效率的提升,以及功能升级的折腾后,终于升级成为数据库设计的老鸟,第一类程序员眼中的高人。这类程序员可以胜任二十个表以上的中型商业数据管理系统的开发工作。他们知道该在什么样的情况下保留一定的冗余数据来提高程序效率,而且其设计的数据库可拓展性较好,当用户需要添加新功能时,原有数据库表只需做少量修改即可。 4、在经历过上十个类似数据库管理软件的重复设计后,第三类程序员中坚持下来没有转行,而是希望从中找出“偷懒”窍门的有心人会慢慢觉悟,从而完成量变到质变的转换。他们所设计的数据库表结构有一定的远见,能够预测到未来功能升级所需要的数据,从而预先留下伏笔。这类程序员目前大多晋级成数据挖掘方面的高级软件开发人员。 5、第三类程序员或第四类程序员,在对现有的各家数据库管理系统的原理和开发都有一定的钻研后,要么在其基础上进行二次开发,要么自行开发一套有自主版权的通用数据库管理系统。 我个人正处于第三类的末期,所以下面所列出的一些设计技巧只适合第二类和部分第三类数据库设计人员。同时,由于我很少碰到有兴趣在这方面深钻下去的同行,所以文中难免出现错误和遗漏,在此先行声明,欢迎大家指正,不要藏私哦8) 一、树型关系的数据表 不少程序员在进行数据库设计的时候都遇到过树型关系的数据,例如常见的类别表,即一个大类,下面有若干个子类,某些子类又有子类这样的情况。当类别不确定,用户希望可以在任意类别下添加新的子类,或者删除某个类别和其下的所有子类,而且预计以后其数量会逐步增长,此时我们就会考虑用一个数据表来保存这些数据。按照教科书上的教导,第二类程序员大概会设计出类似这样的数据表结构: 类别表_1(Type_table_1) 名称 类型 约束条件 说明 type_id int 无重复 类别标识,主键 type_name char(50) 不允许为空 类型名称,不允许重复 type_father int 不允许为空 该类别的父类别标识,如果是顶节点的话设定为某个唯一值 这样的设计短小精悍,完全满足3NF,而且可以满足用户的所有要求。是不是这样就行呢?答案是NO!Why? 我们来估计一下用户希望如何罗列出这个表的数据的。对用户而言,他当然期望按他所设定的层次关系一次罗列出所有的类别,例如这样: 总类别 类别1 类别1.1 类别1.1.1 类别1.2 类别2 类别2.1 类别3 类别3.1 类别3.2 …… 看看为了实现这样的列表显示(树的先序遍历),要对上面的表进行多少次检索?注意,尽管类别1.1.1可能是在类别3.2之后添加的记录,答案仍然是N次。这样的效率对于少量的数据没什么影响,但是日后类型扩充到数十条甚至上百条记录后,单单列一次类型就要检索数十次该表,整个程序的运行效率就不敢恭维了。或许第二类程序员会说,那我再建一个临时数组或临时表,专门保存类型表的先序遍历结果,这样只在第一次运行时检索数十次,再次罗列所有的类型关系时就直接读那个临时数组或临时表就行了。其实,用不着再去分配一块新的内存来保存这些数据,只要对数据表进行一定的扩充,再对添加类型的数量进行一下约束就行了,要完成上面的列表只需一次检索就行了。下面是扩充后的数据表结构: 类别表_2(Type_table_2) 名称 类型 约束条件 说明 type_id int 无重复 类别标识,主键 type_name char(50) 不允许为空 类型名称,不允许重复 type_father int 不允许为空 该类别的父类别标识,如果是顶节点的话设定为某个唯一值 type_layer char(6) 限定3层,初始值为000000 类别的先序遍历,主要为减少检索数据库的次数 按照这样的表结构,我们来看看上面例子记录在表中的数据是怎样的: type_id type_name type_father type_layer 1 总类别 0 000000 2 类别1 1 010000 3 类别1.1 2 010100 4 类别1.2 2 010200 5 类别2 1 020000 6 类别2.1 5 020100 7 类别3 1 030000 8 类别3.1 7 030100 9 类别3.2 7 030200 10 类别1.1.1 3 010101 …… 现在按type_layer的大小来检索一下:SELECT * FROM Type_table_2 ORDER BY type_layer 列出记录集如下: type_id type_name type_father type_layer 1 总类别 0 000000 2 类别1 1 010000 3 类别1.1 2 010100 10 类别1.1.1 3 010101 4 类别1.2 2 010200 5 类别2 1 020000 6 类别2.1 5 020100 7 类别3 1 030000 8 类别3.1 7 030100 9 类别3.2 7 030200 …… 现在列出的记录顺序正好是先序遍历的结果。在控制显示类别的层次时,只要对type_layer字段中的数值进行判断,每2位一组,如大于0则向右移2个空格。当然,我这个例子中设定的限制条件是最多3层,每层最多可设99个子类别,只要按用户的需求情况修改一下type_layer的长度和位数,即可更改限制层数和子类别数。其实,上面的设计不单单只在类别表中用到,网上某些可按树型列表显示的论坛程序大多采用类似的设计。 或许有人认为,Type_table_2中的type_father字段是冗余数据,可以除去。如果这样,在插入、删除某个类别的时候,就得对type_layer 的内容进行比较繁琐的判定,所以我并没有消去type_father字段,这也正符合数据库设计中适当保留冗余数据的来降低程序复杂度的原则,后面我会举一个故意增加数据冗余的案例。 二、商品信息表的设计 假设你是一家百货公司电脑部的开发人员,某天老板要求你为公司开发一套网上电子商务平台,该百货公司有数千种商品出售,不过目前仅打算先在网上销售数十种方便运输的商品,当然,以后可能会陆续在该电子商务平台上增加新的商品出售。现在开始进行该平台数据库的商品信息表的设计。每种出售的商品都会有相同的属性,如商品编号,商品名称,商品所属类别,相关信息,供货厂商,内含件数,库存,进货价,销售价,优惠价。你很快就设计出4个表:商品类型表(Wares_type),供货厂商表(Wares_provider),商品信息表(Wares_info): 商品类型表(Wares_type) 名称 类型 约束条件 说明 type_id int 无重复 类别标识,主键 type_name char(50) 不允许为空 类型名称,不允许重复 type_father int 不允许为空 该类别的父类别标识,如果是顶节点的话设定为某个唯一值 type_layer char(6) 限定3层,初始值为000000 类别的先序遍历,主要为减少检索数据库的次数 供货厂商表(Wares_provider) 名称 类型 约束条件 说明 provider_id int 无重复 供货商标识,主键 provider_name char(100) 不允许为空 供货商名称 商品信息表(Wares_info) 名称 类型 约束条件 说明 wares_id int 无重复 商品标识,主键 wares_name char(100) 不允许为空 商品名称 wares_type int 不允许为空 商品类型标识,和Wares_type.type_id关联 wares_info char(200) 允许为空 相关信息 provider int 不允许为空 供货厂商标识,和Wares_provider.provider_id关联 setnum int 初始值为1 内含件数,默认为1 stock int 初始值为0 库存,默认为0 buy_price money 不允许为空 进货价 sell_price money 不允许为空 销售价 discount money 不允许为空 优惠价 你拿着这3个表给老板检查,老板希望能够再添加一个商品图片的字段,不过只有一部分商品有图片。OK,你在商品信息表(Wares_info)中增加了一个haspic的BOOL型字段,然后再建了一个新表——商品图片表(Wares_pic): 商品图片表(Wares_pic) 名称 类型 约束条件 说明 pic_id int 无重复 商品图片标识,主键 wares_id int 不允许为空 所属商品标识,和Wares_info.wares_id关联 pic_address char(200) 不允许为空 图片存放路径 程序开发完成后,完全满足老板目前的要求,于是正式启用。一段时间后,老板打算在这套平台上推出新的商品销售,其中,某类商品全部都需添加“长度”的属性。第一轮折腾来了……当然,你按照添加商品图片表的老方法,在商品信息表(Wares_info)中增加了一个haslength的BOOL型字段,又建了一个新表——商品长度表(Wares_length): 商品长度表(Wares_length) 名称 类型 约束条件 说明 length_id int 无重复 商品图片标识,主键 wares_id int 不允许为空 所属商品标识,和Wares_info.wares_id关联 length char(20) 不允许为空 商品长度说明 刚刚改完没多久,老板又打算上一批新的商品,这次某类商品全部需要添加“宽度”的属性。你咬了咬牙,又照方抓药,添加了商品宽度表(Wares_width)。又过了一段时间,老板新上的商品中有一些需要添加“高度”的属性,你是不是开始觉得你所设计的数据库按照这种方式增长下去,很快就能变成一个迷宫呢?那么,有没有什么办法遏制这种不可预见性,但却类似重复的数据库膨胀呢?我在阅读《敏捷软件开发:原则、模式与实践》中发现作者举过类似的例子:7.3 “Copy”程序。其中,我非常赞同敏捷软件开发这个观点:在最初几乎不进行预先设计,但是一旦需求发生变化,此时作为一名追求卓越的程序员,应该从头审查整个架构设计,在此次修改中设计出能够满足日后类似修改的系统架构。下面是我在需要添加“长度”的属性时所提供的修改方案: 去掉商品信息表(Wares_info)中的haspic字段,添加商品额外属性表(Wares_ex_property)和商品额外信息表(Wares_ex_info)2个表来完成添加新属性的功能。 商品额外属性表(Wares_ex_property) 名称 类型 约束条件 说明 ex_pid int 无重复 商品额外属性标识,主键 p_name char(20) 不允许为空 额外属性名称 商品额外信息表(Wares_ex_info) 名称 类型 约束条件 说明 ex_iid int 无重复 商品额外信息标识,主键 wares_id int 不允许为空 所属商品标识,和Wares_info.wares_id关联 property_id int 不允许为空 商品额外属性标识,和Wares_ex_property.ex_pid关联 property_value char(200) 不允许为空 商品额外属性值 在商品额外属性表(Wares_ex_property)中添加2条记录: ex_pid p_name 1 商品图片 2 商品长度 再在整个电子商务平台的后台管理功能中追加一项商品额外属性管理的功能,以后添加新的商品时出现新的属性,只需利用该功能往商品额外属性表(Wares_ex_property)中添加一条记录即可。不要害怕变化,被第一颗子弹击中并不是坏事,坏的是被相同轨道飞来的第二颗、第三颗子弹击中。第一颗子弹来得越早,所受的伤越重,之后的抵抗力也越强 三、多用户及其权限管理的设计 开发数据库管理类的软件,不可能不考虑多用户和用户权限设置的问题。尽管目前市面上的大、中型的后台数据库系统软件都提供了多用户,以及细至某个数据库内某张表的权限设置的功能,我个人建议:一套成熟的数据库管理软件,还是应该自行设计用户管理这块功能,原因有二: 1.那些大、中型后台数据库系统软件所提供的多用户及其权限设置都是针对数据库的共有属性,并不一定能完全满足某些特例的需求; 2.不要过多的依赖后台数据库系统软件的某些特殊功能,多种大、中型后台数据库系统软件之间并不完全兼容。否则一旦日后需要转换数据库平台或后台数据库系统软件版本升级,之前的架构设计很可能无法重用。 下面看看如何自行设计一套比较灵活的多用户管理模块,即该数据库管理软件的系统管理员可以自行添加新用户,修改已有用户的权限,删除已有用户。首先,分析用户需求,列出该数据库管理软件所有需要实现的功能;然后,根据一定的联系对这些功能进行分类,即把某类用户需使用的功能归为一类;最后开始建表: 功能表(Function_table) 名称 类型 约束条件 说明 f_id int 无重复 功能标识,主键 f_name char(20) 不允许为空 功能名称,不允许重复 f_desc char(50) 允许为空 功能描述 用户组表(User_group) 名称 类型 约束条件 说明 group_id int 无重复 用户组标识,主键 group_name char(20) 不允许为空 用户组名称 group_power char(100) 允许为空 用户组权限表,内容为功能表f_id的集合 用户表(User_table) 名称 类型 约束条件 说明 user_id int 无重复 用户标识,主键 user_name char(20) 无重复 用户名 user_pwd char(20) 不允许为空 用户密码 user_type int 不允许为空 所属用户组标识,和User_group.group_id关联 采用这种用户组的架构设计,当需要添加新用户时,只需指定新用户所属的用户组;当以后系统需要添加新功能或对旧有功能权限进行修改时,只用操作功能表和用户组表的记录,原有用户的功能即可相应随之变化。当然,这种架构设计把数据库管理软件的功能判定移到了前台,使得前台开发相对复杂一些。但是,当用户数较大(10人以上),或日后软件升级的概率较大时,这个代价是值得的。 四、简洁的批量m:n设计 碰到m:n的关系,一般都是建立3个表,m一个,n一个,m:n一个。但是,m:n有时会遇到批量处理的情况,例如到图书馆借书,一般都是允许用户同时借阅n本书,如果要求按批查询借阅记录,即列出某个用户某次借阅的所有书籍,该如何设计呢?让我们建好必须的3个表先: 书籍表(Book_table) 名称 类型 约束条件 说明 book_id int 无重复 书籍标识,主键 book_no char(20) 无重复 书籍编号 book_name char(100) 不允许为空 书籍名称 …… 借阅用户表(Renter_table) 名称 类型 约束条件 说明 renter_id int 无重复 用户标识,主键 renter_name char(20) 不允许为空 用户姓名 …… 借阅记录表(Rent_log) 名称 类型 约束条件 说明 rent_id int 无重复 借阅记录标识,主键 r_id int 不允许为空 用户标识,和Renter_table.renter_id关联 b_id int 不允许为空 书籍标识,和Book_table.book_id关联 rent_date datetime 不允许为空 借阅时间 …… 为了实现按批查询借阅记录,我们可以再建一个表来保存批量借阅的信息,例如: 批量借阅表(Batch_rent) 名称 类型 约束条件 说明 batch_id int 无重复 批量借阅标识,主键 batch_no int 不允许为空 批量借阅编号,同一批借阅的batch_no相同 rent_id int 不允许为空 借阅记录标识,和Rent_log.rent_id关联 batch_date datetime 不允许为空 批量借阅时间 这样的设计好吗?我们来看看为了列出某个用户某次借阅的所有书籍,需要如何查询?首先检索批量借阅表(Batch_rent),把符合条件的的所有记录的rent_id字段的数据保存起来,再用这些数据作为查询条件带入到借阅记录表(Rent_log)中去查询。那么,有没有什么办法改进呢?下面给出一种简洁的批量设计方案,不需添加新表,只需修改一下借阅记录表(Rent_log)即可。修改后的记录表(Rent_log)如下: 借阅记录表(Rent_log) 名称 类型 约束条件 说明 rent_id int 无重复 借阅记录标识,主键 r_id int 不允许为空 用户标识,和Renter_table.renter_id关联 b_id int 不允许为空 书籍标识,和Book_table.book_id关联 batch_no int 不允许为空 批量借阅编号,同一批借阅的batch_no相同 rent_date datetime 不允许为空 借阅时间 …… 其中,同一次借阅的batch_no和该批第一条入库的rent_id相同。举例:假设当前最大rent_id是64,接着某用户一次借阅了3本书,则批量插入的3条借阅记录的batch_no都是65。之后另外一个用户租了一套碟,再插入出租记录的rent_id是68。采用这种设计,查询批量借阅的信息时,只需使用一条标准T_SQL的嵌套查询即可。当然,这种设计不符合3NF,但是和上面标准的3NF设计比起来,哪一种更好呢?答案就不用我说了吧。 五、冗余数据的取舍 上篇的“树型关系的数据表”中保留了一个冗余字段,这里的例子更进一步——添加了一个冗余表。先看看例子:我原先所在的公司为了解决员工的工作餐,和附近的一家小餐馆联系,每天吃饭记账,费用按人数平摊,月底由公司现金结算,每个人每个月的工作餐费从工资中扣除。当然,每天吃饭的人员和人数都不是固定的,而且,由于每顿工作餐的所点的菜色不同,每顿的花费也不相同。例如,星期一中餐5人花费40元,晚餐2人花费20,星期二中餐6人花费36元,晚餐3人花费18元。为了方便计算每个人每个月的工作餐费,我写了一个简陋的就餐记账管理程序,数据库里有3个表: 员工表(Clerk_table) 名称 类型 约束条件 说明 clerk_id int 无重复 员工标识,主键 clerk_name char(10) 不允许为空 员工姓名 每餐总表(Eatdata1) 名称 类型 约束条件 说明 totle_id int 无重复 每餐总表标识,主键 persons char(100) 不允许为空 就餐员工的员工标识集合 eat_date datetime 不允许为空 就餐日期 eat_type char(1) 不允许为空 就餐类型,用来区分中、晚餐 totle_price money 不允许为空 每餐总花费 persons_num int 不允许为空 就餐人数 就餐计费细表(Eatdata2) 名称 类型 约束条件 说明 id int 无重复 就餐计费细表标识,主键 t_id int 不允许为空 每餐总表标识,和Eatdata1.totle_id关联 c_id int 不允许为空 员工标识标识,和Clerk_table.clerk_id关联 price money 不允许为空 每人每餐花费 其中,就餐计费细表(Eatdata2)的记录就是把每餐总表(Eatdata1)的一条记录按就餐员工平摊拆开,是个不折不扣的冗余表。当然,也可以把每餐总表(Eatdata1)的部分字段合并到就餐计费细表(Eatdata2)中,这样每餐总表(Eatdata1)就成了冗余表,不过这样所设计出来的就餐计费细表重复数据更多,相比来说还是上面的方案好些。但是,就是就餐计费细表(Eatdata2)这个冗余表,在做每月每人餐费统计的时候,大大简化了编程的复杂度,只用类似这么一条查询语句即可统计出每人每月的寄餐次数和餐费总帐: SELECT clerk_name AS personname,COUNT(c_id) as eattimes,SUM(price) AS ptprice FROM Eatdata2 JOIN Clerk_tabsle ON (c_id=clerk_id) JOIN eatdata1 ON (totleid=tid) WHERE eat_date>=CONVERT(datetime,'"&the_date&"') AND eat_date< P> 想象一下,如果不用这个冗余表,每次统计每人每月的餐费总帐时会多麻烦,程序效率也够呛。那么,到底什么时候可以增加一定的冗余数据呢?我认为有2个原则: 1、用户的整体需求。当用户更多的关注于,对数据库的规范记录按一定的算法进行处理后,再列出的数据。如果该算法可以直接利用后台数据库系统的内嵌函数来完成,此时可以适当的增加冗余字段,甚至冗余表来保存这些经过算法处理后的数据。要知道,对于大批量数据的查询,修改或删除,后台数据库系统的效率远远高于我们自己编写的代码。 2、简化开发的复杂度。现代软件开发,实现同样的功能,方法有很多。尽管不必要求程序员精通绝大部分的开发工具和平台,但是还是需要了解哪种方法搭配哪种开发工具的程序更简洁,效率更高一些。冗余数据的本质就是用空间换时间,尤其是目前硬件的发展远远高于软件,所以适当的冗余是可以接受的。不过我还是在最后再强调一下:不要过多的依赖平台和开发工具的特性来简化开发,这个度要是没把握好的话,后期维护升级会栽大跟头的。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!369.entry
我们通常会在应用中碰到树形结构的内容,比如 文件夹/文件模型, 部门组织结构,目录树等等,通常在设计模式中叫做 compose 模式。
在数据库中常常这样表示: 我们以Catalog (分级目录) 为例子
Catalog (分级目录) | 字段名称 | 字段 | 类型 | 备注 | 目录ID | catalog_id | varchar(36) | pk, not null | 目录名称 | catalog_name | varchar(50) | not null | 父目录ID | parent_id | varchar(36) | fk | 创建时间 | create_datetime | datetime | not null | 目录描述 | description | varchar(200) | | 我们考虑在数据库中一次将所有数据读入内存,然后在内存中生成一个Tree,这样可以减少数据库的访问,增加性能,并且只有的数据方式改变的时候,全部重新从数据库中生成Tree,否则一直保持在内存中。
我们使用标准的DAO模式先生成 POJO类(Catalog)和DAO类(CatalogDAO)。
然后我们建立相对通用的 Tree 和 TreeNode 类。 Tree.java package com.humpic.helper.tree;
import java.util.*;
import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory;
public abstract class Tree { protected static Log log = LogFactory.getLog(Tree.class);
private Map treeNodeMaps = new Hashtable(); private TreeNode root;
/** * root if it's parent is empty */ protected void reload(List nodes) { log.info("tree will start reload all data"); synchronized (this) { // initialize treeNodeMaps.clear(); root = null;
List treeNodes = new Vector(nodes.size()); for (int i = 0; i < nodes.size(); i++) { TreeNode node = this.transform(nodes.get(i)); // transform treeNodes.add(node); node.setTree(this); treeNodeMaps.put(node.getNodeId(), node); } for (int i = 0; i < treeNodes.size(); i++) { TreeNode node = (TreeNode) treeNodes.get(i); String parentId = node.getParentId(); if (this.isRootNode(node)) { if (root == null) { root = node; } else { log.error("find more then one root node. ignore."); } } else { TreeNode parent = (TreeNode) treeNodeMaps.get(parentId); if (parent != null) { parent.addChild(node); node.setParent(parent); } else { log.warn("node [id=" + node.getNodeId() + "]: missing parent node."); } } } }
if (root == null) { log.error("the root node is not be defined"); } }
protected boolean isRootNode(TreeNode node) { return StringUtils.isBlank(node.getParentId()); } public TreeNode getRootNode() { return root; }
public TreeNode getTreeNode(String nodeId) { return (TreeNode) treeNodeMaps.get(nodeId); }
public void addTreeNode(TreeNode node) { synchronized (this) { treeNodeMaps.put(node.getNodeId(), node);
String parentId = node.getParentId(); if (StringUtils.isNotBlank(parentId)) { TreeNode parent = getTreeNode(parentId); if (parent != null) { parent.addChild(node); node.setParent(parent); } else { log.error("parent cannot be found: " + node.getParentId()); } } else { if (root == null) { root = node; } else { log.error("find more then one root node. ignore."); } } } }
public void deleteTreeNode(String nodeId) { synchronized (this) { TreeNode node = getTreeNode(nodeId); if (node == null) throw new IllegalArgumentException(nodeId + " cannot be found.");
if (node.getParent() == null) { root = null; treeNodeMaps.clear(); log.warn("the root node has been removed."); } else { node.getParent().getChildren().remove(node);
treeNodeMaps.remove(nodeId); List children = node.getAllChildren(); for (int i = 0; i < children.size(); i++) { TreeNode n = (TreeNode) children.get(i); treeNodeMaps.remove(n.getNodeId()); } } } }
/** * <pre> * Usage: Office -> * * public TreeNode transform(Object info) { * OfficeInfo office_info = (OfficeInfo) info; * TreeNode node = new TreeNode(); * node.setNodeId(office_info.getOfficeId()); * node.setParentId(office_info.getParentId()); * node.setBindData(office_info); * return node; * } * </pre> */ protected abstract TreeNode transform(Object info); }
TreeNode.java package com.humpic.helper.tree;
import java.util.List; import java.util.Vector;
import org.apache.commons.collections.Predicate; import org.apache.commons.lang.ObjectUtils;
public class TreeNode {
private Tree tree; private TreeNode parent; private List children = new Vector(); private List childrenGroup = new Vector(); private String nodeId; private String parentId; private Object bindData;
public String getNodeId() { return nodeId; }
public void setNodeId(String nodeId) { this.nodeId = nodeId; }
public String getParentId() { return parentId; }
public void setParentId(String parentId) { this.parentId = parentId; }
public Object getBindData() { return bindData; }
public void setBindData(Object bindData) { this.bindData = bindData; }
public Tree getTree() { return tree; }
public void setTree(Tree tree) { this.tree = tree; }
public void setParent(TreeNode parent) { this.parent = parent; }
public TreeNode getParent() { return this.parent; }
public List getChildren() { return this.children; }
public void addChild(TreeNode node) { children.add(node); }
/** * get all children, and chilren's children */ public List getAllChildren() { if (this.childrenGroup.isEmpty()) { synchronized (this.tree) { for (int i = 0; i < this.children.size(); i++) { TreeNode node = (TreeNode) this.children.get(i); this.childrenGroup.add(node); this.childrenGroup.addAll(node.getAllChildren()); } } } return this.childrenGroup; }
/** * get all children, and chilren's children */ public List getAllChildren(Predicate predicate) { List groups = new Vector(); fillAllChildren(groups, predicate); return groups; }
private void fillAllChildren(List groups, Predicate predicate) { for (int i = 0; i < this.children.size(); i++) { TreeNode node = (TreeNode) this.children.get(i); if (predicate.evaluate(node)) { groups.add(node); node.fillAllChildren(groups, predicate); } } }
/** * get all parents, and parent's parent */ public List getParents() { List results = new Vector(); TreeNode parent = this.getParent(); while (parent != null) { results.add(parent); parent = parent.getParent(); } return results; }
/** * A.isMyParent(B) == B is A' parent ? <br> * root.isMyParent(null) == true; <br> * root.isMyParent(*) == false <br> * *.isMyParent(null) == false */ public boolean isMyParent(String nodeId) { TreeNode target = tree.getTreeNode(nodeId); TreeNode parent = this.getParent(); if (parent == null) { return target == null; } else { return parent.equals(target); } }
/** * A.isMyAncestor(B) == B is A' ancestor ? <br> * *.isMyAncestor(null) == true; */ public boolean isMyAncestor(String nodeId) { TreeNode target = tree.getTreeNode(nodeId); if (target == null) return true;
return target.getAllChildren().contains(this); }
/** * A.isMyBrother(B) == B is A' brother ? <br> * *.isMyBrother(null) == false */ public boolean isMyBrother(String nodeId) { TreeNode target = tree.getTreeNode(nodeId); if (target == null) return false;
TreeNode p1 = this.getParent(); TreeNode p2 = target.getParent(); return ObjectUtils.equals(p1, p2); }
}
然后建立业务 Tree CatalogTree.java package com.humpic.helper.tree;
import java.util.List; import org.apache.commons.collections.Predicate;
public class CatalogTree extends Tree { private static CatalogTree instance = null;
private CatalogTree() {} public static synchronized CatalogTree getInstance() { if (instance == null) { instance = new CatalogTree(); instance.reloadCatalogs(); } return instance; }
protected TreeNode transform(Object info) { Catalog catalog = (Catalog) info; TreeNode node = new TreeNode(); node.setNodeId(catalog.getCatalogId()); node.setParentId(catalog.getParentId()); node.setBindData(catalog); return node; }
public void reloadCatalogs() { List nodes = CatalogDAO.getInstance().findAll(); super.reload(nodes); }
public Catalog getCatalogNode(String catalogId) { TreeNode node = super.getTreeNode(catalogId); return node == null ? null : (Catalog) node.getBindData(); } }
最后,我们只要使用以下的语句就可以了: 1. CatalogTree.getInstance().getTreeNode(...) 2. CatalogTree.getInstance().getCatalogNode(...) 3. CatalogTree.getInstance().getRootNode()
然后通过 TreeNode,就可以得到 parent, parents 和 children, allChildren 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!367.entry
上篇blog讲了一下unicode等编码的问题﹐不过并没有涉及程序﹐所以这次就用.net来证实一下上次的这些东东。 在证明那些东东之前﹐首先把.net中关于处理encoding,二进制,16进制,byte等相关类别和方法罗列一下。 1.byte与string(那些255以内的整数)的相互转换(各种进制之间的相互转换) 使用System.Convert类别 string to byte Convert.ToByte(string,base) base:2表示二进制,8表示八进制,10表示十进制,16表示十六进制(你要输入33,呵呵﹐异常) 这样可以把字符串的(0--255)转成一个byte Convert.ToByte("01000001",2)转成 65 Convert.ToByte("255",10)转成255 Convert.ToByte("42",16)转成66 同理﹐byte to string也是Convert类 Convert.ToString(byte,base) 同样可以转成相应的进制表示的字符串 通过这两个方法﹐我们要进行2,8,10,16进制的相互转换就容易了 2.char,int,long,boolean等与byte[]之间的相互转换(这些数据在内存中的存储状况) 使用System.BitConverter类别 我们都知道char,int,long等基本类型是以字节形式存在内存中的﹐所以要查看其内存存储方式则直接使用BitConverter.GetBytes()就可以了 然后再使用BitConverter.ToString(byte[])就可以以string方式查看了(如:f9-03表示2个字节) string是由char组成的﹐只要foreach(char in string)就可以看到string的存储方式了(实验表明﹐string在内存中是以unicode编码存在的,下有示例) 3.各种Encoding之间的转换 使用System.Text中的Encoding相关的类别就可以了 包括Encoding,ASCIIEncoding,UTF8Encoding等,当然也可以通过Encoding.GetEncoding()来获取不同的编码。 然后再通过GetBytes(string)方法﹐就可以获取string的不同编码的byte数组了 通过GetString(byte[])方法﹐就可以把某种编码的byte数组转成字符串. 如"I am 小生,hello world!"的各种bytes编码测试 using System; using System.Collections; using System.Text;
public class MyClass { public static void Main() { string tmp = "I am 小生,hello world!"; WL("内存中存储的字节数组﹕");
foreach(char c in tmp) { byte[] b = BitConverter.GetBytes(c); Console.Write(BitConverter.ToString(b) + "-"); } WL(""); WL("unicode字节数组﹕"); byte[] bs1 = Encoding.Unicode.GetBytes(tmp); WL(BitConverter.ToString(bs1)); WL("utf8字节数组﹕");
byte[] bs2 = Encoding.UTF8.GetBytes(tmp); WL(BitConverter.ToString(bs2)); WL("default字节数组﹕"); byte[] bs3 = Encoding.Default.GetBytes(tmp); WL(BitConverter.ToString(bs3)); WL("big5字节数组﹕"); byte[] bs4 = Encoding.GetEncoding(950).GetBytes(tmp); WL(BitConverter.ToString(bs4)); RL(); }
private static void WL(string text, params object[] args) { Console.WriteLine(text, args); }
private static void RL() { Console.ReadLine(); }
private static void Break() { System.Diagnostics.Debugger.Break(); } }
在下面开始之前﹐先摘录一段关于BOM的知识 ----------------------------------------------------------------- UTF的字节序和BOM UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”? Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。 这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。 UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。 Windows就是使用BOM来标记文本文件的编码方式的。 ---------------------------------------------------------- 好了﹐这些问题解决后﹐我们就来做单纯的文本文件的编码识别﹐读取与写入测试吧。 以windows的notepad为例(其它的文本文件读取软件的原理应该也差不多﹐只是会多一些特殊的判断算法而已)。 notepad默认有四种编码来存储和读取文本文件。分别是﹕ ANSI,Unicode,Unicode-big-endian和UTF-8。 首先来讲ANSI吧﹐这个是windows操作系统在区域与语言块设置的编码(也就是系统默认的编码)﹐因此像繁体操作系统就是big5,而简体操作系统则是GBK。 而Unicode和UTF-8这两种格式相信大家已经有所了解(当然前者是unicode-16) 而Unicode-big-endian是什么意思呢﹐它与Unicode几乎一样﹐只是它把高位放在前面(而后者则刚好相反) 上面的摘录已经有所说明﹐这里再解释一下﹕ 如同样是字符"A"﹐在以下几种格式中的存储形式分别是﹕ UTF-16 big-endian : 00 41 UTF-16 little-endian : 41 00 UTF-32 big-endian : 00 00 00 41 UTF-32 little-endian : 41 00 00 00 好了﹐大家想一想﹐文本文件在硬盘中是以字节形式存储的﹐如果不知道文本文件的编码﹐那是无论如何也不能正确读出文本文件显示给用户看的(乱码了只有人才知道﹐程序则认为一切正常) 根据BOM的规则﹐因此在一段字节流开始时﹐如果接收到以下字节﹐则分别表明了该文本文件的编码。 UTF-8: EF BB BF UTF-16 : FF FE UTF-16 big-endian: FE FF UTF-32 little-endian: FF FE 00 00 UTF-32 big-endian: 00 00 FE FF 而如果不是以这个开头﹐那程序则会以ANSI,也就是系统默认编码读取。 所以现在我们来做个测试就可以很清楚地对以上的东东进行验证了。 1.用notepad输入"汉A"这2个字符﹐然后分别保存成ANSI,Unicode,Unicode-big-endian和UTF-8,名字分别取为ansi.txt,unicode.txt,unicode_b.txt,utf8.txt,并且放在c盘根目录下 2.用以下程序进行验证 using System; using System.Collections; using System.IO;
public class MyClass { private static void writefile(string path) { FileStream fs = null; try{ fs = new FileStream(path,FileMode.Open); byte[] bs = new byte[fs.Length]; fs.Read(bs,0,bs.Length); WL(BitConverter.ToString(bs)); SixTTwo(BitConverter.ToString(bs)); } catch(Exception ex) { WL(ex.ToString()); } finally { if(fs!=null) fs.Close(); } }
public static void Main() { string path; WL("ANSI文件格式的字节流﹕"); path = "c:\\ansi.txt"; writefile(path);
WL("Unicode文件格式的字节流﹕"); path = "c:\\unicode.txt"; writefile(path);
WL("Unicode-big-endian文件格式的字节流﹕"); path = "c:\\unicode_b.txt"; writefile(path);
WL("utf-8文件格式的字节流﹕"); path = "c:\\utf8.txt"; writefile(path); RL(); }
public static void SixTTwo(string sixstr) { string[] tmp = sixstr.Split(new char[]{'-'}); foreach(string s in tmp) {
Console.Write(Convert.ToString(Convert.ToByte(s,16),2).PadLeft(8,'0')+ "
"); } WL(""); }
private static void WL(string text, params object[] args) { Console.WriteLine(text, args); }
private static void RL() { Console.ReadLine(); }
private static void Break() { System.Diagnostics.Debugger.Break(); } }
3.以下是输出格式﹕ ANSI文件格式的字节流﹕ BA-BA-41 10111010 10111010 01000001 Unicode文件格式的字节流﹕ FF-FE-49-6C-41-00 11111111 11111110 01001001 01101100 01000001 00000000 Unicode-big-endian文件格式的字节流﹕ FE-FF-6C-49-00-41 11111110 11111111 01101100 01001001 00000000 01000001 utf-8文件格式的字节流﹕ EF-BB-BF-E6-B1-89-41 11101111 10111011 10111111 11100110 10110001 10001001 01000001 从以上结果可以很容易的看到BABA正是"汉"字的gb2312编码﹐当然我的操作系统是繁体的﹐如果我直接双击打开﹐则可以看到"荦A"﹐这是乱码﹐因为我的系统baba查的是big5﹐而baba的big5码正是"荦" 然而还有其它很多程序﹐像IE呀,它可以使用meta标签来识别文件的编码,xml也是可以通过encoding属性来说明文件的编码的﹐所以这些程序的识别方法和普通的又有些不同罢了。 同样﹐写一个文本文件时﹐先写入这些标记符﹐则也会帮助notepad识别这些文件的编码(当然.net专门提供了一些类别﹐如StreamWriter﹐可以直接存成某种编码的格式)。 至于各种encoding之间的转换﹐我想也不必多说了﹐通过Encoding类的Convert,GetBytes和GetString方法是很容易进行转换的。 原来潜水看别人的文章时发现很简单﹐自己写起来才发现写好一篇blog这么困难(汗...) 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!341.entry
Unicode On Wiki:http://www.wiki.cn/wiki/Unicode前几天朋友在做用PHP做图片的水印功能,说是一定要用到汉字的Unicode的编码,才能显出汉字,和他说了半天,仔细想想,自己也没有清楚 unicode是什么一回事,所以就找一点资料学习一下,没有学过操作系统,和其它一些基本的计算机的东西,看来自己要恶补一下了!
Unicode是一种字符编码规范 。
先从ASCII说起。ASCII是用来表示英文字符的一种编码规范,每个ASCII字符占用1个字节(8bits)
因此,ASCII编码可以表示的最大字符数是256,其实英文字符并没有那么多,一般只用前128个(最高位为0),其中包括了控制字符、数字、 大小写字母和其他一些符号 。
而最高位为1的另128个字符被成为“扩展ASCII”,一般用来存放英文的制表符、部分音标字符等等的一些其他符号
这种字符编码规范显然用来处理英文没有什么问题 。(实际上也可以用来处理法文、德文等一些其他的西欧字符,但是不能和英文通用), 但是面对中文、阿拉伯文之类复杂的文字,255个字符显然不够用
于是,各个国家纷纷制定了自己的文字编码规范,其中中文的文字编码规范叫做“GB2312-80”,它是和ASCII兼容的一种编码规范,其实就是 利用扩展ASCII没有真正标准化这一点,把一个中文字符用两个扩展ASCII字符来表示。
但是这个方法有问题,最大的问题就是,中文文字没有真正属于自己的编码,因为扩展ASCII码虽然没有真正的标准化,但是PC里的ASCII码还 是有一个事实标准的(存放着英文制表符),所以很多软件利用这些符号来画表格。这样的软件用到中文系统中,这些表格符就会被误认作中 文字,破坏版面。而且,统计中英文混合字符串中的字数,也是比较复杂的,我们必须判断一个ASCII码是否扩展,以及它的下一个ASCII是否 扩展,然后才“猜”那可能是一个中文字 。
总之当时处理中文是很痛苦的。而更痛苦的是GB2312是国家标准,台湾当时有一个Big5编码标准,很多编码和GB是相同的,所以……,嘿嘿。
这时候,我们就知道,要真正解决中文问题,不能从扩展ASCII的角度入手,也不能仅靠中国一家来解决。而必须有一个全新的编码系统,这 个系统要可以将中文、英文、法文、德文……等等所有的文字统一起来考虑,为每个文字都分配一个单独的编码,这样才不会有上面那种现象 出现。
于是,Unicode诞生了。
Unicode有两套标准,一套叫UCS-2(Unicode-16),用2个字节为字符编码,另一套叫UCS-4(Unicode-32),用4个字节为字符编码。
以目前常用的UCS-2为例,它可以表示的字符数为2^16=65535,基本上可以容纳所有的欧美字符和绝大部分的亚洲字符 。
UTF-8的问题后面会提到 。
在Unicode里,所有的字符被一视同仁。汉字不再使用“两个扩展ASCII”,而是使用“1个Unicode”,注意,现在的汉字是“一个字符”了, 于是,拆字、统计字数这些问题也就自然而然的解决了 。
但是,这个世界不是理想的,不可能在一夜之间所有的系统都使用Unicode来处理字符,所以Unicode在诞生之日,就必须考虑一个严峻的问题 :和ASCII字符集之间的不兼容问题。
我们知道,ASCII字符是单个字节的,比如“A”的ASCII是65。而Unicode是双字节的,比如“A”的Unicode是0065,这就造成了一个非常大的 问题:以前处理ASCII的那套机制不能被用来处理Unicode了 。
另一个更加严重的问题是,C语言使用’\0′作为字符串结尾,而Unicode里恰恰有很多字符都有一个字节为0,这样一来,C语言的字符串函数将 无法正常处理Unicode,除非把世界上所有用C写的程序以及他们所用的函数库全部换掉 。
于是,比Unicode更伟大的东东诞生了,之所以说它更伟大是因为它让Unicode不再存在于纸上,而是真实的存在于我们大家的电脑中。那就是 :UTF 。
UTF= UCS Transformation Format UCS转换格式
它是将Unicode编码规则和计算机的实际编码对应起来的一个规则。现在流行的UTF有2种:UTF-8和UTF-16 。
其中UTF-16和上面提到的Unicode本身的编码规范是一致的,这里不多说了。而UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII 编码保持最大程度的兼容 。
UTF-8有点类似于Haffman编码,它将Unicode编码为00000000-0000007F的字符,用单个字节来表示;
00000080-000007FF的字符用两个字节表示
00000800-0000FFFF的字符用3字节表示
因为目前为止Unicode-16规范没有指定FFFF以上的字符,所以UTF-8最多是使用3个字节来表示一个字符。但理论上来说,UTF-8最多需要用6字 节表示一个字符。
在UTF-8里,英文字符仍然跟ASCII编码一样,因此原先的函数库可以继续使用。而中文的编码范围是在0080-07FF之间,因此是2个字节表示 (但这两个字节和GB编码的两个字节是不同的),用专门的Unicode处理类可以对UTF编码进行处理。
下面说说中文的问题。
由于历史的原因,在Unicode之前,一共存在过3套中文编码标准。
GB2312-80,是中国大陆使用的国家标准,其中一共编码了6763个常用简体汉字。Big5,是台湾使用的编码标准,编码了台湾使用的繁体汉字, 大概有8千多个。HKSCS,是中国香港使用的编码标准,字体也是繁体,但跟Big5有所不同。
这3套编码标准都采用了两个扩展ASCII的方法,因此,几套编码互不兼容,而且编码区间也各有不同
因为其不兼容性,在同一个系统中同时显示GB和Big5基本上是不可能的。当时的南极星、RichWin等等软件,在自动识别中文编码、自动显示正 确编码方面都做了很多努力 。
他们用了怎样的技术我就不得而知了,我知道好像南极星曾经以同屏显示繁简中文为卖点。
后来,由于各方面的原因,国际上又制定了针对中文的统一字符集GBK和GB18030,其中GBK已经在Windows、Linux等多种操作系统中被实现。
GBK兼容GB2312,并增加了大量不常用汉字,还加入了几乎所有的Big5中的繁体汉字。但是GBK中的繁体汉字和Big5中的几乎不兼容。
GB18030相当于是GBK的超集,比GBK包含的字符更多。据我所知目前还没有操作系统直接支持GB18030。
谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词 这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理 这篇文章的动机是两个问题:
问题一: 使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows 是怎样识别编码方式的呢?
我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF (Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?
问题二: 最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方 式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。 查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量 做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。
0、big endian和little endian big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面, 还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由 此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。
1、字符编码、内码,顺带介绍汉字编码 字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序 员设计了用于简体中文的GB2312和用于繁体中文的big5。
GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位 是72*94=6768。其中有5个空位是D7FA-D7FE。
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的 GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平 台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。
从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。 在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属 于双字节字符集 (DBCS)。
有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常 我们还是用GBK指代中文Windows内码。
这里还有一些细节:
GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。
在DBCS中,GB内码的存储格式始终是big endian,即高位在前。
GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不 影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位 是什么。
2、Unicode、UCS和UTF 前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容), 与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。
Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是”Universal Multiple
-Octet Coded Character Set”,简称为UCS。UCS可以看作是”Unicode Character Set”的缩写。
根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一 个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。
在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。 从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。
目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。
UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、 UTF-7、UTF-16。
IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是Internet Engine ering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。
3、UCS-2、UCS-4、BMP
UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。 下面让我们做一些简单的数学游戏:
UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。
UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还 没有任何字符被分配在BMP之外。
4、UTF编码
UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:
UCS-2编码(16进制) UTF-8 字节流(二进制) 0000 - 007F 0xxxxxxx 0080 - 07FF 110xxxxx 10xxxxxx 0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制 是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
读者可以用记事本测试一下我们的编码是否正确。
UTF-16以16位为单元对UCS进行编码。对于小于0×10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0×10000的UCS码 ,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0×10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。 但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。
5、UTF的字节序和BOM UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序 。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙 ”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想 法:
在UCS编码中有一个叫做”ZERO WIDTH NO-BREAK SPACE”的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传 输中。UCS规范建议我们在传输字节流前,先传输字符”ZERO WIDTH NO-BREAK SPACE”。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符”ZERO WIDTH NO-BREAK SPACE”又被称作BOM。
UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符”ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是EF BB BF(读者可以用 我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了 文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!340.entry
To make our example programs more interesting, we want to accept input and properly format the program output. Of course, modern programs use a GUI for collecting user input. However, programming such an interface requires more tools and techniques than we have at our disposal at this time. Because the first order of business is to become more familiar with the Java programming language, we make do with the humble console for input and output for now. GUI programming is covered in Chapters 7 through 9. 要是我们的程序更有意思,我们需要在程序中接收输入,并适当的格式化输出。当然,先进的程序采用GUI(图形用户界面)来收集用户输入。但是编写这样一个界面需要更多的工具和技术,这已经超出了我们现在的目标。因为我们当前的目标是更熟悉Java编程语言,我们还是采用粗陋的控制台作为输入输出。GUI编程将在7~9章介绍。 Reading Input 读取输入 You saw that it is easy to print output to the "standard output stream" (that is, the console window) just by calling System.out.println. Oddly enough, before JDK 5.0, there was no convenient way to read input from the console window. Fortunately, that situation has finally been rectified. 你可以看到,向标准输出流(也就是控制台窗口)输出内容非常简单,只需要调用System.out.println即可。非常奇怪的是,JDK5.0以前,还没有什么便捷的办法可以从控制台窗口读取输入。幸运的是,这种情况最终得以矫正。 To read console input, you first construct a Scanner that is attached to the "standard input stream" System.in. 要读取控制台输入,你首先构造一个附属于标准输入流System.in的Scanner。 Scanner in = new Scanner(System.in); Now you use the various methods of the Scanner class to read input. For example, the nextLine method reads a line of input. 现在你可以使用Scanner类的各种方法来读取输入。例如,nextLine方法可以读取一行输入。 System.out.print("What is your name? "); String name = in.nextLine(); Here, we use the nextLine method because the input might contain spaces. To read a single word (delimited by whitespace), call 这里,我们使用nextLine方法因为输入可能包含空格。要读取单个单词(以空格分隔),调用: String firstName = in.next(); To read an integer, use the nextInt method. 要读取一个整数,使用nextInt方法。 System.out.print("How old are you? "); int age = in.nextInt(); Similarly, the nextdouble method reads the next floating-point number. 类似的,nextdouble方法读取下一个浮点数字。 The program in Example 3-2 asks for the user's name and age and then prints a message like Hello, Cay. Next year, you'll be 46 Finally, add the line import java.util.*; at the beginning of the program. The Scanner class is defined in the java.util package. Whenever you use a class that is not defined in the basic java.lang package, you need to use an import directive. We look at packages and import directives in more detail in Chapter 4. 例3-2要求用户输入姓名和年龄,并输出消息”Hello, cay. Next year, you’ll be 46” 最后在程序的开头加上import java.util.*; Scanner类是定义在java.util包里的。只要你使用了一个没有定义在基本的java.lang包中的类,你都需要一个import 命令。我们将在第四章中更详细的讨论包和import。 Example 3-2. InputTest.java 1. import java.util.*; 2. 3. public class InputTest 4. { 5. public static void main(String[] args) 6. { 7. Scanner in = new Scanner(System.in); 8. 9. // get first input 10. System.out.print("What is your name? "); 11. String name = in.nextLine(); 12. 13. // get second input 14. System.out.print("How old are you? "); 15. int age = in.nextInt(); 16. 17. // display output on console 18. System.out.println("Hello, " + name + ". Next year, you'll be " + (age + 1)); 19. } 20. } NOTE If you do not have JDK 5.0 or above, you have to work harder to read user input. The simplest method is to use an input dialog (see Figure 3-6). 如果你没有JDK 5.0或以上版本,你必须在读取用户输入上多费些力气。最简单的方法就是使用一个输入框(见图3-6) String input = JOptionPane.showInputDialog(promptString) Figure 3-6. An input dialog The return value is the string that the user typed. 返回值就是用户输入的字符串。 For example, here is how you can query the name of the user of your program: 例如,下面的代码展示了我们如何在程序中要求输入用户的名字。 String name = JOptionPane.showInputDialog("What is your name?"); Reading numbers requires an additional step. The JOptionPane.showInputDialog method returns a string, not a number. You use the Integer.parseInt or Double.parseDouble method to convert the string to its numeric value. For example, 读取数字型输入需要再多一步。JOptionPane.showInputDialog方法返回一个字符串,而不是一个数字。你需要使用Integer.parseInt或者Double.parseDouble方法来将其数字值转换为字符串。例如 String input = JOptionPane.showInputDialog("How old are you?"); int age = Integer.parseInt(input); If the user types 45, then the string variable input is set to the string "45". The Integer.parseInt method converts the string to its numeric value, the number 45. 如果用户输入45,则字符串变量被赋值为”45”。Integer.parseInt方法将字符串转换为相应的数字值,也就是数字45。 The JOptionPane class is defined in the javax.swing package, so you need to add the statement JOptionPane类是定义在javax.swing包中,所以你需要添加语句 import javax.swing.*; Finally, whenever your program calls JOptionPane.showInputDialog, you need to end it with a call to System.exit(0). The reason is a bit technical. Showing a dialog box starts a new thread of control. When the main method exits, the new thread does not automatically terminate. To end all threads, you call the System.exit method. (For more information on threads, see Chapter 1 of Volume 2.) The following program is the equivalent to Example 3-2 prior to JDK 5.0. 最后,无论何时程序调用JOptionPane.showInputDialog,你需要采用System.exit(0)来结束之。这是一个有点技术性的原因。显示一个对话框需要启动一个新的控制线程。当main方法退出时,新启动的线程并没有终结。要结束所有线程,你需要调用System.exit方法(想进一步了解线程,参见卷2第一章)下面的程序等同于JDK5.0以前的例3-2 import javax.swing.*; public class InputTest { public static void main(String[] args) { String name = JOptionPane.showInputDialog("What is your name?"); String input = JOptionPane.showInputDialog("How old are you?"); int age = Integer.parseInt(input); System.out.println("Hello, " + name + ". Next year, you'll be " + (age + 1)); System.exit(0); } } java.util.Scanner 5.0 constructs a Scanner object from the given input stream. reads the next line of input. reads the next word of input (delimited by whitespace). - int nextInt()
- double nextDouble()
read and convert the next character sequence that represents an integer or floating-point number. tests whether there is another word in the input.检测后面是否还有一个单词 - boolean hasNextInt()
- boolean hasNextDouble()
test whether the next character sequence represents an integer or floating-point number.检测下一个字符序列是否代表一个整数或一个浮点数。 javax.swing.JOptionPane 1.2 - static String showInputDialog(Object message)
displays a dialog box with a message prompt, an input field, and "OK" and "Cancel" buttons. The method returns the string that the user typed. 显示一个带有消息提示的对话框,一个输入框,还有”OK”和”Cancel”按钮。该方法返回用户键入的字符串。 java.lang.System 1.0 - static void exit(int status)
terminates the virtual machine and passes the status code to the operating system. By convention, a non-zero status code indicates an error. 终止虚拟机并向操作系统传递状态码。约定非零状态码表示一种错误。 Formatting Output 格式化输出 You can print a number x to the console with the statement System.out.print(x). That command will print x with the maximum number of non-zero digits for that type. For example, 你可以用System.out.print(x)语句将一个数字x输出到控制台。这个命令将以该类型最大非零位数打印x。例如: double x = 10000.0 / 3.0; System.out.print(x); prints打印出 3333.3333333333335 That is a problem if you want to display, for example, dollars and cents.但是当你想显示美元和美分的时候这就成了问题。 Before JDK 5.0, formatting numbers was a bit of a hassle. Fortunately, JDK 5.0 brought back the venerable printf method from the C library. For example, the call JDK5.0前,对数字的格式化产生了争论。幸运的是,JDK5.0将古老的printf方法从C库中带回来了。例如: System.out.printf("%8.2f", x); prints x with a field width of 8 characters and a precision of 2 characters. That is, the printout contains a leading space and the seven characters 以8个字符的字段宽度和2个字符的精度打印出x。也就是说,输出包含前导空白和七个字符 3333.33 You can supply multiple parameters to printf, for example:你可以在printf中使用多个参数。 System.out.printf("Hello, %s. Next year, you'll be %d", name, age); Each of the format specifiers that start with a % character is replaced with the corresponding argument. The conversion character that ends a format specifier indicates the type of the value to be formatted: f is a floating-point number, s a string, and d a decimal integer. Table 3-5 shows all conversion characters. 每个格式区分符以%开始,并被相应的变量取代。结束格式区分符的转换字符指明了要格式化的值的类型:f是指一个浮点数字,s是指一个字符串,d是指一个十进制整数。表3-5列出了所有的转换字符。 Table 3-5. Conversions for printf Conversion Character Type Example d Decimal integer十进制整数 159 x Hexadecimal integer十六进制整数 9f o Octal integer八进制整数 237 f Fixed-point floating-point定点浮点数 15.9 e Exponential floating-point指数型浮点数 1.59e+01 g General floating-point (the shorter of e and f) a Hexadecimal floating point十六进制浮点 0x1.fccdp3 s String Hello c Character H b Boolean TRue h Hash code哈希码 42628b2 tx Date and time See Table 3-7 % The percent symbol百分号 % n The platform-dependent line separator换行符 Table 3-7. Date and Time Conversion Characters Conversion Character Type Example C Complete date and time Mon Feb 09 18:05:19 PST 2004 F ISO 8601 date 2004-02-09 D U.S. formatted date (month/day/year) 02/09/2004 T 24-hour time 18:05:19 r 12-hour time 06:05:19 pm R 24-hour time, no seconds 18:05 Y Four-digit year (with leading zeroes) 2004 y Last two digits of the year (with leading zeroes) 04 C First two digits of the year (with leading zeroes) 20 B Full month name February b or h Abbreviated month name月份名称缩写 Feb m Two-digit month (with leading zeroes) 02 d Two-digit day (with leading zeroes) 09 e Two-digit day (without leading zeroes) 9 A Full weekday name Monday a Abbreviated weekday name Mon j Three-digit day of year (with leading zeroes), between 001 and 366 069 H Two-digit hour (with leading zeroes), between 00 and 23 18 k Two-digit hour (without leading zeroes), between 0 and 23 18 I Two-digit hour (with leading zeroes), between 01 and 12 06 l Two-digit hour (without leading zeroes), between 1 and 12 6 M Two-digit minutes (with leading zeroes) 05 S Two-digit seconds (with leading zeroes) 19 L Three-digit milliseconds (with leading zeroes)三位毫秒数 047 N Nine-digit nanoseconds (with leading zeroes)九位纳秒数 047000000 P Uppercase morning or afternoon marker大写上下午标记 PM p Lowercase morning or afternoon marker小写上下午标记 pm z RFC 822 numeric offset from GMT格林尼治时间偏移量 -0800 Z Time zone时区 PST s Seconds since 1970-01-01 00:00:00 GMT 1078884319 E Milliseconds since 1970-01-01 00:00:00 GMT 1078884319047 In addition, you can specify flags that control the appearance of the formatted output. Table 3-6 shows all flags. For example, the comma flag adds group separators. That is, 另外,你可以指定标记来控制格式化输出的行为。例如逗号标记可以添加组分隔符。也就是 System.out.printf("%,.2f", 10000.0 / 3.0); Table 3-6. Flags for printf Flag Purpose Example + Prints sign for positive and negative numbers输出数字的正负号 +3333.33 space Adds a space before positive numbers在整数前加一个空格 | 3333.33| 0 Adds leading zeroes加入前导的0 003333.33 - Left-justifies field左对齐字段 |3333.33 | ( Encloses negative number in parentheses用圆括号括起负数 (3333.33) , Adds group separators添加组分隔符 3,333.33 # (for f format) Always includes a decimal point总是包含小数点 3,333. # (for x or o format) Adds 0x or 0 prefix添加0x或者0前缀 0xcafe ^ Converts to upper case转换为大写 0XCAFE $ Specifies the index of the argument to be formatted; for example, %1$d %1$x prints the first argument in decimal and hexadecimal指定要格式化的参数的索引,例如%1$d %1$x以十进制和十六进制形式输出第一个参数 159 9F < Formats the same value as the previous specification; for example, %d %<x prints the same number in decimal and hexadecimal格式化前面指定的相同的值,例如%d %<x 以十进制和十六进制的形式输出同一个数字。 159 9F prints打印出 3,333.33 You can use multiple flags, for example, "%,(.2f", to use group separators and enclose negative numbers in parentheses. 你可以使用多个标记,例如"%,(.2f",使用组分隔符并用圆括号括住负数。 NOTE You can use the s conversion to format arbitrary objects. If an arbitrary object implements the Formattable interface, the object's formatTo method is invoked. Otherwise, the toString method is invoked to turn the object into a string. We discuss the toString method in Chapter 5 and interfaces in Chapter 6. 你可以使用s转换来格式化任意对象。如果任意对象实现了Formattable接口,则对象的formatTo方法被调用。否则调用toString方法来将一个对象转变为一个字符串。我们将在第五章和第六章中分别讨论toString方法和接口 You can use the static String.format method to create a formatted string without printing it: 你可使用静态方法String.format来创建格式化的字符串,而并不将其输出。 String message = String.format("Hello, %s. Next year, you'll be %d", name, age); Although we do not describe the Date type in detail until Chapter 4, we do, in the interest of completeness, briefly discuss the date and time formatting options of the printf method. You use two a two-letter format, starting with t and ending in one of the letters of Table 3-7. For example, 尽管我们到第4章才讲述Date类型,但是完全是出于兴趣,简要介绍一下printf方法的时间和日期格式选项。你可以使用一个两字母的格式,以t开始,以表3-7中的字母结束。例如 System.out.printf("%tc", new Date()); prints the current date and time in the format 以如下格式输出日期和时间。 Mon Feb 09 18:05:19 PST 2004 As you can see in Table 3-7, some of the formats yield only a part of a given date, for example, just the day or just the month. It would be a bit silly if you had to supply the date multiple times to format each part. For that reason, a format string can indicate the index of the argument to be formatted. The index must immediately follow the %, and it must be terminated by a $. For example, 如你在表3-7中所见,一些格式仅输出指定时间的一部分,例如,仅仅是日或者月。如果你要多次提供时间,并要格式化每个部分,这看起来很傻。一个格式化字串可以指定要格式化的参数的索引。索引值紧跟百分号%,并以$结束。例如 System.out.printf("%1$s %2$tB %2$te, %2$tY", "Due date:", new Date()); prints输出 Due date: February 9, 2004 Alternatively, you can use the < flag. It indicates that the same argument as in the preceding format specification should be used again. That is, the statement 或者你可以选择使用<标记。它表明将再次使用前一个格式所规范的参数。也就是如下语句 System.out.printf("%s %tB %<te, %<tY", "Due date:", new Date()); yields the same output as the preceding statement. 将和上一条语句产生相同的输出。 CAUTION Argument index values start with 1, not with 0: %1$... formats the first argument. This avoids confusion with the 0 flag. 参数索引值以1开始,而不是0. You have now seen all features of the printf method. Figure 3-7 shows a syntax diagram for format specifiers. 下图是格式化说明符的语法: Figure 3-7. Format specifier syntax [View full size image] NOTE A number of the formatting rules are locale specific. For example, in Germany, the decimal separator is a period, not a comma, and Monday is formatted as Montag. You will see in Volume 2 how to control the international behavior of your applications. 一些格式化规范是地区性的。例如,在德国,十进制分隔符是一个句点,而不是一个逗号,而Monday被格式化为Montag。你将在卷2中看到如何在你的程序中处理国际化问题。 TIP If you use a version of Java prior to JDK 5.0, use the NumberFormat and DateFormat classes instead of printf. 如果你使用的是JDK5.0以前版本,请使用NumberFormat和DateFormat来代替printf 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!335.entry
Conceptually, Java strings are sequences of Unicode characters. For example, the string "Java\u2122" consists of the five Unicode characters J, a, v, a, and ™. Java does not have a built-in string type. Instead, the standard Java library contains a predefined class called, naturally enough, String. Each quoted string is an instance of the String class: 概念上讲,Java字符串就是Unicode字符序列。例如,字符串"Java\u2122"由5个Unicode字符J,a,v,a和™组成。Java没有内建的string类型。但是,标准Java库提供了一个类,很自然的,叫做String。每个被引起来的字符串就是一个String实例: String e = ""; // an empty string String greeting = "Hello"; Code Points and Code Units 代码点和代码单元 Java strings are implemented as sequences of char values. As we discussed on page 41, the char data type is a code unit for representing Unicode code points in the UTF-16 encoding. The most commonly used Unicode characters can be represented with a single code unit. The supplementary characters require a pair of code units. Java字符串是以char值序列的方式实现的。如我们在41页中提到的,char数据类型是一个表示UTF-16编码中各个Unicode代码点的代码单元。最常用的Unicode字符可以用一个单独的代码单元表示。增补字符需要一对代码单元。 The length method yields the number of code units required for a given string in the UTF-16 encoding. For example: length方法返回指定的UTF-16编码字符串所需代码单元的数量,例如: String greeting = "Hello"; int n = greeting.length(); // is 5. To get the true length, that is, the number of code points, call 要得到真实的长度,即代码点的数量,调用: int cpCount = greeting.codePointCount(0, greeting.length()); The call s.charAt(n) returns the code unit at position n, where n is between 0 and s.length() – 1. For example, s.charAt(n) 返回位置n对应的代码单元,这里n介于0和s.length()-1之间。例如: char first = greeting.charAt(0); // first is 'H' char last = greeting.charAt(4); // last is 'o' To get at the ith code point, use the statements 要获得第i个代码点,使用语句: int index = greeting.offsetByCodePoints(0, i); int cp = greeting.codePointAt(index); NOTE Java counts the code units in strings in a peculiar fashion: the first code unit in a string has position 0. This convention originated in C, where there was a technical reason for counting positions starting at 0. That reason has long gone away and only the nuisance remains. However, so many programmers are used to this convention that the Java designers decided to keep it. Java以一种特殊的方式计算字符串中的代码单元:字符串中的第一个代码单元的位置是0。这个约定源于C,在C中位置从0开始计数是有技术原因的。这个技术原因现在早已不存在了,但是却留下了这个令人讨厌的方式。但是由于很多程序员习惯了这个约定,所以Java的设计者们决定保留它。 Why are we making a fuss about code units? Consider the sentence 我们为什么要在代码单元上小题大做?看一下这个句子 is the set of integers The character requires two code units in the UTF-16 encoding. Calling 在UTF-16编码中,字符需要两个代码单元,调用 char ch = sentence.charAt(1) doesn't return a space but the second code unit of . To avoid this problem, you should not use the char type. It is too low-level. 并不返回一个空格,而是其第二个代码单元。要避免这个问题,你不应当使用char类型。这个类型太低级。 If your code traverses a string, and you want to look at each code point in turn, use these statements: 如果你的代码遍历一个字符串,并且你想逐个查看每个代码单元,请使用下面的语句: int cp = sentence.codePointAt(i); if (Character.isSupplementaryCodePoint(cp)) i += 2; else i++; Fortunately, the codePointAt method can tell whether a code unit is the first or second half of a supplementary character, and it returns the right result either way. That is, you can move backwards with the following statements: 幸运的是,codePointAt方法可以告诉我们何处是一个辅助字符的前一半或者后一半,并且对于任一一种都可以返回正确的结果。也就是说,你也可以用下面的语句进行逆向遍历 i--; int cp = sentence.codePointAt(i); if (Character.isSupplementaryCodePoint(cp)) i--; Substrings 子串 You extract a substring from a larger string with the substring method of the String class. For example, 用substring方法可以从一个大的字符串中提取字串。例如 String greeting = "Hello"; String s = greeting.substring(0, 3); creates a string consisting of the characters "Hel". 得到一个由字符”Hel”组成的字串。 The second parameter of substring is the first code unit that you do not want to copy. In our case, we want to copy the code units in positions 0, 1, and 2 (from position 0 to position 2 inclusive). As substring counts it, this means from position 0 inclusive to position 3 exclusive. substring的第二个参数是你第一个不想复制的代码单元。在我们的例子中,我们想复制的是位置0、1、2(从位置0到位置2,包含端点)。也就是从位置0(包含)到位置3(不包含)。 There is one advantage to the way substring works: Computing the number of code units in the substring is easy. The string s.substring(a, b) always has b - a code units. For example, the substring "Hel" has 3 – 0 = 3 code units. substring的这种工作方式有一个优点:计算字串中的代码单元数量是简单的。字符串s.substring(a,b)的代码单元数总是等于b-a。从例子即可看出。 String Editing 字符串编辑 The String class gives no methods that let you change a character in an existing string. If you want to turn greeting into "Help!", you cannot directly change the last positions of greeting into 'p' and '!'. If you are a C programmer, this will make you feel pretty helpless. How are you going to modify the string? In Java, it is quite easy: concatenate the substring that you want to keep with the characters that you want to replace. Java中的String类型虽然不提供字符串编辑的方法,但是,你可以采用将某个字符串的字串和其他字串相连接的方式。例如你希望将”Hello”修改为”Help!”,你可以这样做 greeting = greeting.substring(0, 3) + "p!"; This declaration changes the current value of the greeting variable to "Help!". Because you cannot change the individual characters in a Java string, the documentation refers to the objects of the String class as being immutable. Just as the number 3 is always 3, the string "Hello" will always contain the code unit sequence describing the characters H, e, l, l, o. You cannot change these values. You can, as you just saw however, change the contents of the string variable greeting and make it refer to a different string, just as you can make a numeric variable currently holding the value 3 hold the value 4. 在Java中,你不能改变Java字串中的某个值,但是,你可以改变变量的内容,即使得字符串变量指向其他字符串。 Isn't that a lot less efficient? It would seem simpler to change the code units than to build up a whole new string from scratch. Well, yes and no. Indeed, it isn't efficient to generate a new string that holds the concatenation of "Hel" and "p!". But immutable strings have one great advantage: the compiler can arrange that strings are shared. 虽然生成新的字符组合效率会降低,但是不可变的字符串有一大优点:编译器可以将字符串共享。 To understand how this works, think of the various strings as sitting in a common pool. String variables then point to locations in the pool. If you copy a string variable, both the original and the copy share the same characters. Overall, the designers of Java decided that the efficiency of sharing outweighs the inefficiency of string editing by extracting substrings and concatenating. 要理解这个工作过程,假设各种字符串处在一个共享池中。字符串变量指向池中的某一个位置。如果你复制一个字符串,原字串和拷贝共享同一个字符序列。总之,Java设计者认为共享的有效性远大于提取字串在连接的字符串编辑的有效性。 Look at your own programs; we suspect that most of the time, you don't change strings—you just compare them. Of course, in some cases, direct manipulation of strings is more efficient. (One example is assembling strings from individual characters that come from a file or the keyboard.) For these situations, Java provides a separate StringBuilder class that we describe in Chapter 12. If you are not concerned with the efficiency of string handling, you can ignore StringBuilder and just use String. 看看我们的程序,大多数时候,我们并不改变字串,而是进行比较。当然,有时候直接对字符串进行操作更为有效。(一个例子就是编译来自于一个文件或者键盘的独立字符序列。)对此种情况而言,Java提供独立的StringBuilder类。我们在12章中讨论。如果你对字符串处理的效率不感兴趣,你可以跳过StringBuilder,仅仅使用String就可以了。 C++ NOTE C programmers generally are bewildered when they see Java strings for the first time because they think of strings as arrays of characters: C程序员第一次看到Java字符串的时候会感到疑惑,因为他们会认为字符串其实就是字符数组: char greeting[] = "Hello"; That is the wrong analogy: a Java string is roughly analogous to a char* pointer, 这是一个错误的类比:Java字符串组略的类同于一个char*指针。 char* greeting = "Hello"; When you replace greeting with another string, the Java code does roughly the following: 当你用另一个字符串代替greeting的时候,Java代码粗略的进行如下工作: char* temp = malloc(6); strncpy(temp, greeting, 3); strncpy(temp + 3, "p!", 3); greeting = temp; Sure, now greeting points to the string "Help!". And even the most hardened C programmer must admit that the Java syntax is more pleasant than a sequence of strncpy calls. But what if we make another assignment to greeting? 当然,现在greeting指向字符串”Help!”。即使是最铁杆的C程序员也必须承认Java语句比使用一组strncpy函数要令人愉快。但如果我们要给greeting另作指派又会如何呢? greeting = "Howdy"; Don't we have a memory leak? After all, the original string was allocated on the heap. Fortunately, Java does automatic garbage collection. If a block of memory is no longer needed, it will eventually be recycled. 这难道不会产生内存泄漏?毕竟,原字串是分配在堆上的。幸运的是,Java具有垃圾自动回收机制。如果某部分内存不再需要,它将最终被回收。 If you are a C++ programmer and use the string class defined by ANSI C++, you will be much more comfortable with the Java String type. C++ string objects also perform automatic allocation and deallocation of memory. The memory management is performed explicitly by constructors, assignment operators, and destructors. However, C++ strings are mutable—you can modify individual characters in a string. 如果你是C++程序员,并且使用由ANSI C++定义的string类,你将感到和Java中的String类型一样的舒服。C++中的string对象也能自动分配和释放内存。内存管理由构造函数、赋值运算符和析构函数清晰的执行。但是C++字符串是可变的,你可以修改字串中的独立字符。 Concatenation 连接 Java, like most programming languages, allows you to use the + sign to join (concatenate) two strings. 和大多数编程语言一样,Java也可以使用+号将两个字符串相连接。 String expletive = "Expletive"; String PG13 = "deleted"; String message = expletive + PG13; The above code sets the variable message to the string "Expletivedeleted". (Note the lack of a space between the words: the + sign joins two strings in the order received, exactly as they are given.) 以上的代码将变量message设置为字符串”Expletivedeleted”。(之所以两个单词之间会缺少空格,是因为+号精确的按照两个单词给出的顺序将其连接起来) When you concatenate a string with a value that is not a string, the latter is converted to a string. (As you see in Chapter 5, every Java object can be converted to a string.) For example: 当你将一个字符串和一个非字符串连接时,后者将转换为字符串(第五章中,你将看到,Java对象都可以转换成字符串),例如: int age = 13; String rating = "PG" + age; sets rating to the string "PG13". 将rating设置为”PG13”。 This feature is commonly used in output statements. For example, 这一功能通常用于输出语句,例如 System.out.println("The answer is " + answer); is perfectly acceptable and will print what one would want (and with the correct spacing because of the space after the word is). 可以很好的接受,并打印出你想要的(由于is 后面有空格,所以也能正确的打印出空格) Testing Strings for Equality 测试字符串相等 To test whether two strings are equal, use the equals method. The expression 要测试两个字符串是否相等,使用equals方法。表达式 s.equals(t) returns TRue if the strings s and t are equal, false otherwise. Note that s and t can be string variables or string constants. For example, the expression 返回true当字符串t和s相等时,否则,返回false。注意,s和t可以是字符串变量,也可以是字符串常量。例如,表达式 "Hello".equals(greeting) is perfectly legal. To test whether two strings are identical except for the upper/lowercase letter distinction, use the equalsIgnoreCase method. 也是很合法的。要测试两个字符串除了大小写的差别是否相同,使用equalsIgnoreCase方法。 "Hello".equalsIgnoreCase("hello") Do not use the == operator to test whether two strings are equal! It only determines whether or not the strings are stored in the same location. Sure, if strings are in the same location, they must be equal. But it is entirely possible to store multiple copies of identical strings in different places. 不要使用==运算符来测试两个字符串是否相等!这种方法仅能判断两个字符串是否存储在同一个位置上。当然,如果字符串存储在同一个位置上,他们肯定相等。但是在不同的位置存储相同的字符串的多个拷贝也是完全有可能的。 String greeting = "Hello"; //initialize greeting to a string if (greeting == "Hello") . . . // probably true if (greeting.substring(0, 3) == "Hel") . . . // probably false If the virtual machine would always arrange for equal strings to be shared, then you could use the == operator for testing equality. But only string constants are shared, not strings that are the result of operations like + or substring. Therefore, never use == to compare strings lest you end up with a program with the worst kind of bug—an intermittent one that seems to occur randomly. 如果虚拟机总是将字符串分配为共享的,那么,你可以使用==运算符来测试相等。但是只有字符串常量是共享的,而那些+或者substring运算产生的字符串则不是共享的。所以,千万不可以使用==来比较字符串,以免你编写出的程序存在最糟糕的一种bug——一种不连续发生的貌似随机的Bug。 C++ NOTE If you are used to the C++ string class, you have to be particularly careful about equality testing. The C++ string class does overload the == operator to test for equality of the string contents. It is perhaps unfortunate that Java goes out of its way to give strings the same "look and feel" as numeric values but then makes strings behave like pointers for equality testing. The language designers could have redefined == for strings, just as they made a special arrangement for +. Oh well, every language has its share of inconsistencies. 如果你习惯了C++的string类,那么你需要特别注意相等性测试。C++中的string类在进行字符串内容的相等比较的时候,运算符==进行了重载。Java打破自己的形式,给字符串赋予数字值一般的外表,而实际上又让这些字符串在比较的时候像指针一样,这也许是个不幸的事情。这门语言的设计者可以重新定义string中的==符号,就像他们特别分配了+号一样。嗯,好吧,每个语言都有其矛盾的一面。 C programmers never use == to compare strings but use strcmp instead. The Java method compareTo is the exact analog to strcmp. You can use C程序员从不使用==来比较字符串,而是使用strcmp。Java中的compareTo方法精确的类似于strcmp,你可以使用 if (greeting.compareTo("Hello") == 0) . . . but it seems clearer to use equals instead. 但是使用equals似乎更清晰明了。 The String class in Java contains more than 50 methods. A surprisingly large number of them are sufficiently useful so that we can imagine using them frequently. The following API note summarizes the ones we found most useful. Java中的String类有多达50个方法。这么多的方法都十分的有用,所以我们可以经常使用它们。下面的API注释中,我们总结了最有用的一些方法 (译者:以下内容不再翻译,仅供参考。) NOTE You will find these API notes throughout the book to help you understand the Java Application Programming Interface (API). Each API note starts with the name of a class such as java.lang.String—the significance of the so-called package name java.lang is explained in Chapter 4. The class name is followed by the names, explanations, and parameter descriptions of one or more methods. We typically do not list all methods of a particular class but instead select those that are most commonly used, and describe them in a concise form. For a full listing, consult the on-line documentation. We also list the version number in which a particular class was introduced. If a method has been added later, it has a separate version number. java.lang.String 1.0 returns the code unit at the specified location. You probably don't want to call this method unless you are interested in low-level code units. - int codePointAt(int index) 5.0
returns the code point that starts or ends at the specified location. - int offsetByCodePoints(int startIndex, int cpCount) 5.0
returns the index of the code point that is cpCount code points away from the code point at startIndex. - int compareTo(String other)
returns a negative value if the string comes before other in dictionary order, a positive value if the string comes after other in dictionary order, or 0 if the strings are equal. - boolean endsWith(String suffix)
returns TRue if the string ends with suffix. - boolean equals(Object other)
returns true if the string equals other. - boolean equalsIgnoreCase(String other)
returns true if the string equals other, except for upper/lowercase distinction. - int indexOf(String str)
- int indexOf(String str, int fromIndex)
- int indexOf(int cp)
- int indexOf(int cp, int fromIndex)
return the start of the first substring equal to the string str or the code point cp, starting at index 0 or at fromIndex, or -1 if str does not occur in this string. - int lastIndexOf(String str)
- int lastIndexOf(String str, int fromIndex)
- int lastindexOf(int cp)
- int lastindexOf(int cp, int fromIndex)
return the start of the last substring equal to the string str or the code point cp, starting at the end of the string or at fromIndex. returns the length of the string. - int codePointCount(int startIndex, int endIndex) 5.0
returns the number of code points between startIndex and endIndex - 1. Unpaired surrogates are counted as code points. - String replace(CharSequence oldString, CharSequence newString)
returns a new string that is obtained by replacing all substrings matching oldString in the string with the string newString. You can supply String or StringBuilder objects for the CharSequence parameters. - boolean startsWith(String prefix)
returns true if the string begins with prefix. - String substring(int beginIndex)
- String substring(int beginIndex, int endIndex)
return a new string consisting of all code units from beginIndex until the end of the string or until endIndex - 1. returns a new string containing all characters in the original string, with uppercase characters converted to lower case. returns a new string containing all characters in the original string, with lowercase characters converted to upper case. returns a new string by eliminating all leading and trailing spaces in the original string. Reading the On-Line API Documentation As you just saw, the String class has lots of methods. Furthermore, there are thousands of classes in the standard libraries, with many more methods. It is plainly impossible to remember all useful classes and methods. Therefore, it is essential that you become familiar with the on-line API documentation that lets you look up all classes and methods in the standard library. The API documentation is part of the JDK. It is in HTML format. Point your web browser to the docs/api/index.html subdirectory of your JDK installation. You will see a screen like that in Figure 3-2. Figure 3-2. The three panes of the API documentation [View full size image] The screen is organized into three frames. A small frame on the top left shows all available packages. Below it, a larger frame lists all classes. Click on any class name, and the API documentation for the class is displayed in the large frame to the right (see Figure 3-3). For example, to get more information on the methods of the String class, scroll the second frame until you see the String link, then click on it. Figure 3-3. Class description for the String class [View full size image] Then scroll the frame on the right until you reach a summary of all methods, sorted in alphabetical order (see Figure 3-4). Click on any method name for a detailed description of that method (see Figure 3-5). For example, if you click on the compareToIgnoreCase link, you get the description of the compareToIgnoreCase method. Figure 3-4. Method summary of the String class [View full size image] Figure 3-5. Detailed description of a String method [View full size image] TIP Bookmark the docs/api/index.html page in your browser right now. 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!330.entry
Bitwise Operators 位运算符 When working with any of the integer types, you have operators that can work directly with the bits that make up the integers. This means that you can use masking techniques to get at individual bits in a number. The bitwise operators are & ("and") | ("or") ^ ("xor") ~ ("not") These operators work on bit patterns. For example, if n is an integer variable, then int fourthBitFromRight = (n & 8) / 8; gives you a 1 if the fourth bit from the right in the binary representation of n is 1, and 0 if not. Using & with the appropriate power of 2 lets you mask out all but a single bit. 在对整数类型进行处理的时,还有能够直接处理构成整数的各个位的运算符。这意味着你可以使用掩码技术来处理一个数字中的各个独立位。这些位运算符是: & ("与") | ("或") ^ ("异或") ~ ("非") 这些运算符工作在比特形式下。例如,如果n是一个整型变量,那么 int fourthBitFromRight = (n & 8) / 8; 这条语句将在n的二进制表示从右起第四位是1时得出结果1,否则为0。使用&符号搭配适当的2的指数幂可以使你屏蔽某一个位以外的所有位。 NOTE 注释 When applied to boolean values, the & and | operators yield a boolean value. These operators are similar to the && and || operators, except that the & and | operators are not evaluated in "short circuit" fashion. That is, both arguments are first evaluated before the result is computed. 当应用于布尔值时,&和|运算将产生一个布尔值。这些运算符和&&以及||运算符是类似的,但是&和|并不以短路形式运算。也就是说,两个参数在结果算出之前就参与运算。(译者注,参见http://www.itsway.net/java/java020505.aspx) There are also >> and << operators, which shift a bit pattern to the right or left. These operators are often convenient when you need to build up bit patterns to do bit masking: int fourthBitFromRight = (n & (1 << 3)) >> 3; Finally, a >>> operator fills the top bits with zero, whereas >> extends the sign bit into the top bits. There is no <<< operator. 还有>>和<<运算符,可以向左或者向右移动一位。这些运算符在你需要通过位模式来执行按位掩码时是非常方便的,例如: int fourthBitFromRight = (n & (1 << 3)) >> 3; 最后,>>>运算符用0填写头两位,而>>把符号为增加到头一位。没有<<<运算符。 CAUTION 注意 The right-hand side argument of the shift operators is reduced modulo 32 (unless the left-hand side is a long, in which case the right-hand side is reduced modulo 64). For example, the value of 1 << 35 is the same as 1 << 3 or 8. 移位运算符右边的参数被减少到32(除非左边的参数较长,这种情况下,右边的较少到64)。例如,1<<35的值和1<<3或者1<<8是一样的。 C++ NOTE C++注释 In C/C++, there is no guarantee as to whether >> performs an arithmetic shift (extending the sign bit) or a logical shift (filling in with zeroes). Implementors are free to choose whatever is more efficient. That means the C/C++ >> operator is really only defined for non-negative numbers. Java removes that ambiguity. 在C/C++中,并不保证>>进行的是算术移位(扩展符号位)还是逻辑移位(以0填充)。设备可以在二者中选择更有效的操作。这就意味着C/C++中的>>运算符的确仅仅是为非负数定义的。Java去除了这种歧义性。 Mathematical Functions and Constants 数学函数与常量 The Math class contains an assortment of mathematical functions that you may occasionally need, depending on the kind of programming that you do. To take the square root of a number, you use the sqrt method: Math类包含一组你有时会用到的数学函数,这取决于你所编程序的类型。要计算一个数字的平方根,需要使用sqrt方法: double x = 4; double y = Math.sqrt(x); System.out.println(y); // 打印 2.0 NOTE 注释 There is a subtle difference between the println method and the sqrt method. The println method operates on an object, System.out, defined in the System class. But the sqrt method in the Math class does not operate on any object. Such a method is called a static method. You can learn more about static methods in Chapter 4. println方法和sqrt方法有一个微妙的差别。println方法对一个对象进行操作,即System类中定义的System.out。但是Math类中的sqrt方法并不对任何对象进行操作。这样的方法称作静态方法。你可在第四章中学习更多有关静态方法的知识。 The Java programming language has no operator for raising a quantity to a power: you must use the pow method in the Math class. The statement double y = Math.pow(x, a); sets y to be x raised to the power a (xa). The pow method has parameters that are both of type double, and it returns a double as well. Java语言没有指数幂运算符,你需要使用Math类中的pow方法。语句 double y = Math.pow(x, a); 将y的值设置为x的a次幂。pow方法的两个参数都应该是double类型,且返回值也是double类型。 The Math class supplies the usual trigonometric functions Math类提供了常用的三角函数: Math.sin Math.cos Math.tan Math.atan Math.atan2 and the exponential function and its inverse, the natural log: 以及指数函数及其逆运算——自然对数: Math.exp Math.log Finally, two constants denote the closest possible approximations to the mathematical constants p and e: 最后,介绍两个与数学常量p 和 e的值非常近似的常量: Math.PI Math.E TIP提示 Starting with JDK 5.0, you can avoid the Math prefix for the mathematical methods and constants by adding the following line to the top of your source file: 从JDK5.0开始,你可以在源文件起始处使用如下代码来避免每次使用数学方法和常量的Math前缀。 import static java.lang.Math.*; For example, 例如: System.out.println("The square root of \u03C0 is " + sqrt(PI)); We discuss static imports in Chapter 4. 我们将在第四章讨论静态导入。 NOTE注意 The functions in the Math class use the routines in the computer's floating-point unit for fastest performance. If completely predictable results are more important than fast performance, use the StrictMath class instead. It implements the algorithms from the "Freely Distributable Math Library" fdlibm, guaranteeing identical results on all platforms. See http://www.netlib.org/fdlibm/index.html for the source of these algorithms. (Whenever fdlibm provides more than one definition for a function, the StrictMath class follows the IEEE 754 version whose name starts with an "e".) 为了获得最快的性能,Math类中的函数使用计算机浮点运算单元中的例程。如果可预测的结果完全重于快速表现,则使用StrictMath类。这个类从“自由分布数学库”中实现运算,保证各个平台上的统一的结果。算法源码参见http://www.netlib.org/fdlibm/index.html。不论何时fdlibm提供对一个函数的多种定义,StrictMath类始终遵循以“e”开始命名的IEEE 754版本。 Conversions Between Numeric Types 数字类型之间的转换 It is often necessary to convert from one numeric type to another. Figure 3-1 shows the legal conversions. 在多种数字类型之间进行转换是常有的事,图3-1说明了合法的转换: 图3-1 数字类型之间的合法转换 The six solid arrows in Figure 3-1 denote conversions without information loss. The three dotted arrows denote conversions that may lose precision. For example, a large integer such as 123456789 has more digits than the float type can represent. When the integer is converted to a float, the resulting value has the correct magnitude but it loses some precision. 图3-1中的六个实箭头表示无信息丢失的转换。三个虚箭头表示可能丢失精度的转换。例如,一个大的整型数字,如123456789,其位数多于浮点型能够表示的位数。当这个整型数转换成浮点型时,结果值仍然很大,但是丢失了精度。 int n = 123456789; float f = n; // f is 1.23456792E8 When two values with a binary operator (such as n + f where n is an integer and f is a floating-point value) are combined, both operands are converted to a common type before the operation is carried out. 当两个数字和一个二进制运算符结合在一起(例如n+f中,n是一个整数,而f是一个浮点数),两个操作数都会在运算之前被转换为通用类型。 - If either of the operands is of type double, the other one will be converted to a double.
- Otherwise, if either of the operands is of type float, the other one will be converted to a float.
- Otherwise, if either of the operands is of type long, the other one will be converted to a long.
- Otherwise, both operands will be converted to an int.
- 如果两个操作数有一个是double型,另一个转换为double型。
- 否则,如果两个操作数有一个为float型,则另一个转换为float型。
- 否则,如果两个操作数有一个为long型,则另一个转换为long型。
- 否则,两个操作数都被转换为整型。
Casts In the preceding section, you saw that int values are automatically converted to double values when necessary. On the other hand, there are obviously times when you want to consider a double as an integer. Numeric conversions are possible in Java, but of course information may be lost. Conversions in which loss of information is possible are done by means of casts. The syntax for casting is to give the target type in parentheses, followed by the variable name. For example: 在前面的章节中,你可以看到整型值在需要的时候可以自动转换为double型,另一方面,有时你也希望把double型转换为整型。在Java中,数字类型的转换是允许的,但是丢失信息也是自然的。丢失信息的转换是依靠强制转换来完成的。强制转换的语句是在变量名前加上由圆括号括起来的目标类型,例如: double x = 9.997; int nx = (int) x; Then, the variable nx has the value 9 because casting a floating-point value to an integer discards the fractional part. 这样,变量nx的值就是0,因为浮点型向整型的强制转换舍弃了小数部分。 If you want to round a floating-point number to the nearest integer (which is the more useful operation in most cases), use the Math.round method: 如果你想把一个浮点数四舍五入成最接近的整数(在大多数情况下,这是更有用的操作),使用Math.round方法: double x = 9.997; int nx = (int) Math.round(x); Now the variable nx has the value 10. You still need to use the cast (int) when you call round. The reason is that the return value of the round method is a long, and a long can only be assigned to an int with an explicit cast because there is the possibility of information loss. 现在,变量nx的值就是10了。在你调用round方法时,你还是需要进行强制转换(int)。原因是round方法的返回值类型是long,而long类型在给int变量赋值时,由于可能产生信息丢失,所以必须采用显式转换。 CAUTION注意 If you try to cast a number of one type to another that is out of the range for the target type, the result will be a truncated number that has a different value. For example, (byte) 300 is actually 44. 如果你试图将某种类型的数字转换为比原类型范围小的目标类型,结果将被截断,产生一个不同的值,例如(byte) 300 的结果实际上是44。 C++ NOTE C++注释 You cannot cast between boolean values and any numeric type. This convention prevents common errors. In the rare case that you want to convert a boolean value to a number, you can use a conditional expression such as b ? 1 : 0. 你不能在布尔型和数字类型之间进行转换。这种转换将产生常见错误。极少数情况下,你希望将布尔值转换为一个数字,此时你可以使用条件表达式,例如b?1:0 Parentheses and Operator Hierarchy 圆括号和运算优先级 Table 3-4 shows the precedence of operators. If no parentheses are used, operations are performed in the hierarchical order indicated. Operators on the same level are processed from left to right, except for those that are right associative, as indicated in the table. For example, because && has a higher precedence than ||, the expression Table 3-4. Operator Precedence Operators Associativity [] . () (method call) Left to right ! ~ ++ -- + (unary) – (unary) () (cast) new Right to left * / % Left to right + - Left to right << >> >>> Left to right < <= > >= instanceof Left to right == != Left to right & Left to right ^ Left to right | Left to right && Left to right || Left to right ?: Right to left = += -= *= /= %= &= |= ^= <<= >>= >>>= Right to left 表3-4展示了运算符的优先级。如果没有使用括号,运算符将按照表中显示的优先级进行运算。除了表中指明的右结合的运算符,同一级别的运算符自左向右运算。例如,&&和优先级高于||,表达式 a && b || c means等于 (a && b) || c Because += associates right to left, the expression 由于+=自右向左结合,表达式 a += b += c means等于 a += (b += c) That is, the value of b += c (which is the value of b after the addition) is added to a. 也就是b+=c的值(即第一次加法后b的值)再加到a上。 C++ NOTE C++注释 Unlike C or C++, Java does not have a comma operator. However, you can use a comma-separated list of expressions in the first and third slot of a for statement. 与C或C++不同,Java没有逗号运算符,但是你可以在一个for语句中的第一个和第三个位置使用一个逗号分隔的表达式。 C语言中的逗号运算符: C语言中逗号“,”也是一种运算符,称为逗号运算符。 其功能是把两个表达式连接起来组成一个表达式, 称为逗号表达式。 其一般形式为: 表达式1,表达式2 其求值过程是分别求两个表达式的值,并以表达式2的值作为整个逗号表达式的值。 void main(){ int a=2,b=4,c=6,x,y; y=(x=a+b),(b+c); printf("y=%d,x=%d",y,x); } a<--2,b<--4,c<--6,x<--0,y<--0 x<--a+b,y<---b+c 本例中,y等于整个逗号表达式的值,也就是表达式2的值,x是第一个表达式的值。对于逗号表达式还要说明几点: 1.逗号表达式一般形式中的表达式1和表达式2 也可以又是逗号表达式。例如: 表达式1,(表达式2,表达式3) 形成了嵌套情形。因此可以把逗号表达式扩展为以下形式: 表达式1,表达式2,…表达式n 整个逗号表达式的值等于表达式n的值。 2.程序中使用逗号表达式,通常是要分别求逗号表达式内各表达式的值,并不一定要求整个逗号表达式的值。 3.并不是在所有出现逗号的地方都组成逗号表达式,如在变量说明中,函数参数表中逗号只是用作各变量之间的间隔符。 以上是摘抄来的 我本人觉得自己最常使用逗号运算符是在 for循环里 for (i = 0, j = 0; i < 3 && j < 3; i++, j+=2) { printf("i = %d, j = %d",i,j); } Enumerated Types 枚举类型 Sometimes, a variable should only hold a restricted set of values. For example, you may sell clothes or pizza in four sizes: small, medium, large, and extra large. Of course, you could encode these sizes as integers 1, 2, 3, 4, or characters S, M, L, and X. But that is an error-prone setup. It is too easy for a variable to hold a wrong value (such as 0 or m). 有时候,一个变量需要仅保存一组有限值的集合。例如,你可能出售四种尺寸的衣服或者匹萨:小号、中号、大号和特大号。当然,你可以给这四种型号编号为1,2,3,4或者S,M,L,X。但这是一个具有错误倾向的设置。变量很可能会承载一个错误的值(比如0或者m)。 Starting with JDK 5.0, you can define your own enumerated type whenever such a situation arises. An enumerated type has a finite number of named values. For example, 从JDK5.0开始,你可以在遇到这种情况时定义自己的枚举类型。一个枚举类型具有有限个数的命名变量。例如: enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE }; Now you can declare variables of this type: 现在,你可以定义这种类型的变量: Size s = Size.MEDIUM; A variable of type Size can hold only one of the values listed in the type declaration or the special value null that indicates that the variable is not set to any value at all. 一个Size类型的变量只能承载Size类型中声明的一个值,或者承载一个特殊值null来表示这个变量没有被设置任何值。 We discuss enumerated types in greater detail in Chapter 5. 我们将在第五章更详细的讨论枚举类型。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!314.entry
Java has the full complement of relational operators. To test for equality you use a double equal sign, ==. For example, the value of Java有完全的关系运算符补充。要测试相等,你可以使用一个双等号,==。比如, 3 == 7 is false. 3==7的值是false。 Use a != for inequality. For example, the value of 使用!=来测试不等。比如: 3 != 7 is true. 3 != 7的值是true。 Finally, you have the usual < (less than), > (greater than), <= (less than or equal), and >= (greater than or equal) operators. 最后,还有普通的< (小于),> (大于),<= (小于等于),和 >= (大于等于) 运算符。 Java, following C++, uses && for the logical "and" operator and || for the logical "or" operator. As you can easily remember from the != operator, the exclamation point ! is the logical negation operator. The && and || operators are evaluated in "short circuit" fashion. The second argument is not evaluated if the first argument already determines the value. If you combine two expressions with the && operator, Java,和C++一样,使用&&表示逻辑与,使用||表示逻辑或。由于你能轻易记得!=运算符,所以感叹号!就表示逻辑非。 && 和 || 运算符采用“短路”方式求值。如果第一个参数的值已经确定,则不需要考虑第二个参数的值。如果你将两个表达式用&&连接, expression1 && expression2 and the truth value of the first expression has been determined to be false, then it is impossible for the result to be TRue. Thus, the value for the second expression is not calculated. This behavior can be exploited to avoid errors. For example, in the expression 并且第一个表达式的真值是false,那么运算结果不可能为true。因此第二个表达式的值不参与运算。该特性可以用来避免错误。例如,在表达式 x != 0 && 1 / x > x + y // no division by 0 the second part is never evaluated if x equals zero. Thus, 1 / x is not computed if x is zero, and no divide-by-zero error can occur. 当中,第二部分如果x等于0的话就不被计算。 Similarly, the value of expression1 || expression2 is automatically true if the first expression is true, without evaluation of the second expression. 类似的,表达式expression1 || expression2 中,如果第一表达式为true,那么整个表达式的值就为true,而不计算第二个表达式的值。 Finally, Java supports the ternary ?: operator that is occasionally useful. The expression 最后,Java支持偶尔很有用的三目运算符?:。表达式 condition ? expression1 : expression2 evaluates to the first expression if the condition is TRue, to the second expression otherwise. For example, 如果condition为ture则计算expression1的值,否则计算expression2的值。例如: x < y ? x : y gives the smaller of x and y. 求得x和y当中较小的一个。 文章来源: http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!312.entry
|
|
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
26 | 27 | 28 | 29 | 30 | 31 | 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 | 1 | 2 | 3 | 4 | 5 | 6 |
|
常用链接
留言簿(6)
随笔分类(28)
随笔档案(90)
文章分类(1)
文章档案(1)
收藏夹(4)
牛人牛博
酷站
最新随笔
搜索
最新评论
阅读排行榜
评论排行榜
|
|