linux 下开发常用命令

************文件和目录*****************

 

pwd

查看自己所在位置

pwd -p /*显示链接到的真实路径*/

ls

列出目录及文件的属性信息

ls -a /*列出所有文件和子目录,包括隐藏文件和目录*/

ls -A /*列出所有文件和目录,但不包括隐藏文件和目录*/

ls -d /*知县是目录本深的属性信息*/

ls -l /*以长格式显示文件和目录的详细信息*/

ls -i /*先是文件的i节点编号*/

ls -h /*显示信息是更人性化容量的大小,如KB,MB,GB等*/

ls -R /*递归显示制定目录下的各级目录及文件*/

ll -h:列表显示目录下的文件,并且显示文件大小.

cat

用于显示指定文件的内容

cat -n /*显示文件内容是在每一行行首添加行号*/

cat -b /*显示文件内容是为非空白添加行号*/

tail

一直跟踪显示文件变化(查看文件末尾指定行数的内容)

tail -N /*N为用户指定的行数*/

tail -f /*跟踪文件中新增加的内容*/

head

用来显示文件开头n行

head -N /*N为用户指定的行数*/

more

分页显示文件内容   (通过空格向下翻页,通过回车一行一行滚动)

more -N /*N为每屏指定的行数*/

less

分页显示文件内容(和more一样。more只能向下翻页,less可以向上翻页。可以通过上下箭头进行上下一行行滚动,也可以通过page up/空格命令向上/向下进行一屏一屏滚动.ctrl+b,用关键字查询按‘n’键可以跳转到下一个关键字)

less 在查看之前不会加载整个文件。可以尝试使用 less 和 vi 打开一个很大的文件,你就会看到它们之间在速度上的区别。

在 less 中导航命令类似于 vi。本文中将介绍一些导航命令以及使用 less 的其它一些技巧。

1 搜索

当使用命令 less file-name 打开一个文件后,可以使用下面的方式在文件中搜索。搜索时整个文本中匹配的部分会被高亮显示。

向前搜索

/ – 使用一个模式进行搜索,并定位到下一个匹配的文本

n – 向前查找下一个匹配的文本

N – 向后查找前一个匹配的文本

向后搜索

? – 使用模式进行搜索,并定位到前一个匹配的文本

n – 向后查找下一个匹配的文本

N – 向前查找前一个匹配的文本

2 全屏导航

ctrl + F – 向前移动一屏

ctrl + B – 向后移动一屏

ctrl + D – 向前移动半屏

ctrl + U – 向后移动半屏

3 单行导航

j – 向前移动一行

k – 向后移动一行

4 其它导航

G – 移动到最后一行

g – 移动到第一行

q / ZZ – 退出 less 命令

5 其它有用的命令

v – 使用配置的编辑器编辑当前文件

h – 显示 less 的帮助文档

&pattern – 仅显示匹配模式的行,而不是整个文件

grep

在文件中查找并显示包含指定字符串的行,查找字符串可使用正则表达式匹配

grep -i /*查找内容是忽略大小写*/

grep -v /*反转查找,技术处于查找条件不相符的行*/

find

在子目录中搜索匹配的文件

find -name /*按文件名称查找,可以只用通配符*/

find -size /*按万大小查找,可以使用K,M等容量单位*/

find -type /*按文件类型查找,类型f,d,l,b,c分别表示普通文件,目录,链接文件,块设备文件,字符设备文件*/

cd

更改工作目录,不带任何选项或参数时,默认切换到用户宿主目录

cd -p /*如果指定的目录为符号链接,则切换到对应的物理路径*/

whereis

查找文件

locate

定位文件

mkdir

创建新目录

mkdir -p /*递归创建多级目录*/

touch

更新文件的时间标记,如文件不存在则建立对应的空文件

cp

复制文件或目录,复制源是目录或多个文件,目标必须是目录

cp -f /*直接强制复制不进行提醒*/

cp -i /*复制时进行提醒确认*/

cp -l /*为源文件建立硬链接,而不是直接复制磁盘数据块*/

cp -p /*复制时保持原文件权限,宿主及时间*/

cp -r /*复制目录时,递归复制所有文件及子目录*/

mv

移动文件或目录,可以在移动时改名。移动多个文件或目录时,目标必须是目录

mv -f /*直接强制移动不进行提醒*/

mv -i /*移动时进行提醒确认*/

rm

删除文件或目录

mv -f /*直接强制删除而不进行提醒*/

mv -i /*删除是时进行提醒确认*/

mv -r /*递归删除所有文件及目录*/

*******************压缩和解压*************

tar

linux传播的文件基本是压缩文件tgz,tar.gz

-c(生成)

-f(文件)

-v(verbose)

-z(压缩)

-t(测试)

-x(展开)

#tar cz(v)f mydir.tar.gz mydir

#tar xz(v)f mydir.tar.gz mydir

–使用示例:

tar -tvzf 20140221130101.log.tgz  –查看该压缩包下是否有文件

tar xfv 20140221130101.log.tgz     –解压到当前文件夹下

******************vi******************

linux/unix下的配置文件都是文本文件

vi是使用最广泛的编辑器

vi分为三种工作模式

-一般模式

-编辑模式

-命令模式

vi常用命令

w q wq wq!

****************远程管理****************

telnet/ssh

ssh使用:

# ssh user1@192.168.0.1 输入密码

scp

 

secure copy (remote file copy program) 也是一个基于SSH安全协议的文件传输命令。与sftp不同的是,它只提供主机间的文件传输功能,没有文件管理的功能。

复制local_file 到远程目录remote_folder下

scp local_file remote_user@host:remote_folder

复制local_folder 到远程remote_folder(需要加参数 -r 递归)

scp –r local_folder remote_user@host:remote_folder

以上命令反过来写就是远程复制到本地

[weblogic@test testdomain]$ scp bbb.txtweblogic@10.10.236.59:/weblogic/wls1036/user_projects/domains/mydomain

 

sz/rz

sz/rz 是基于ZModem传输协议的命令。对传输的数据会进行核查,并且有很好的传输性能。使用起来更是非常方便,但前提是window端需要有能够支持ZModem的telnet或者SSH客户端,例如secureCRT。

首先需要在secureCRT中可以配置相关的本地下载和上传目录,然后用rz、sz命令即可方便的传输文件数据。

下载数据到本地下载目录:sz filename1 filename2 …

上传数据到远程:执行rz –be 命令,客户端会弹出上传窗口,用户自行选择(可多选)要上传的文件即可。

 

************系统相关***********************

ps

以静态快照方式输出当前运行的进程的状态统计数据

格式:ps [选项]

ps a /*显示当前终端下的所有进程信息,包括其他用户信息*/

ps u /*使用以用户为主的格式输出进程信息*/

ps x /*显示当前用户在所有终端下的进程信息*/

ps -e /*显示系统内所有进程信息*/

ps -l /*使用长格式显示进程信息*/

ps -f /*使用完整格式显示进程信息*/

top   动态显示当前运行的进程信息

对于多个或者多核的CPU,top显示的是多个CPU所占用的百分比总和,进入top视图后按1可以按核来显示消耗情况。

us表示用户进程占用百分比,sy表示内核线程(系统线程)处理所占百分比,ni表示nice命令改变优先级的任务所占的百分比,id表示CPU空闲时所占的百分比,wa表示为在执行过程中等待IO所占的百分比,hi表示为硬件中断所占的百分比,si表示为软件中断所占的百分比.

在top视图中按shift+h可以按线程查看CPU的消耗情况。默认情况top视图显示的为进程的CPU消耗情况.

格式:top [-d] 时间

top -d /*指定动态信息的刷新间隔时间,单位为秒。也可以使用ss.tt格式*/

pidstat:

pidstat是sysstat中的工具如需要使用pidstat必须先安装sysstat工具。

如:pidstat 1 2

pidstat -p[PID] -t 1 5

vmstat:

pstree

以树型结构查看系统中的进程及其相互关系(进程数)

格式:pstree [-aup]

pstree -a /*显示进程对应的命令行*/

pstree -u /*显示进程对应的用户名信息*/

pstree -p /*显示进程对应的进程号信息*/

kill

终止指定进程号的进程,PID可以通过ps命令获得

格式:kill [-9] <PID号>

KILL -9 /*无条件强行终止进程*/

killall

与kill命令相似

格式:killall [-9] <进程名>

free

查看系统内存,交互空间的使用情况

格式:free  [-bkm]

free -b /*以Byte为单位显示信息*/

free -k /*以Kb为单位显示信息*/

free -m /*以Mb为单位显示信息*/

**************************环境相关*********************

echo

显示指定的字符串或变量的内容,不指定任何参数时将显示一个空行

read

从标准输入读入一个字符串并赋值给指定的变量

read -p /*在接受输入内容前显示提示字符串的内容,以增强交互性*/

export

将局部变量导出为全局变量或取消设置,允许在命令中直接为变量赋值

export -p /*查看当前命令环境中导出的全局变量

export -n /*取消全局变量的全局属性*/

exit

退出当前shell程序或命令环境,并返回一个退出状态的数字(0为正常,1为异常

********************************按名称查询文件******************************************
[root@emed4test /]# find / -name install.log
#####################查看当前线程################################
[root@jyyy ~]# ps -aux
5、查看进程情况
[root@localhost /]# ps -ef|grep rpc
********************************显示某一进程实时耗用的资源************************************************
top -p processId  显示某一进程实时耗用的资源
********************************显示包含该字符串的文件路径******************************************
[root@emed4test /]# locate install.html.
**********************************linux快捷键***************************
———————————————————–
1、将光标移到行首   Ctrl + a
2、将光标移到行尾   Ctrl + e
3、擦除光标前的整行 Ctrl + u
4、擦除光标后的整行 Ctrl + k
5、删除光标前的一个词 Ctrl + w  (以空格分隔)
6、清屏             Ctrl + l
7、杀掉当前进程     ctrl + c
8、重起             Ctrl + Alt + Del
*****************************************用vi修改文件**************************************
先启动shell:  bash
vi filename
切换模式                   i/esc    (修改模式显示insert,命令模式无显示)
输入命令冒号加行号数字     :1   即跳到第一行
跳到文件尾ctrl g
找某个关键字用             /关键字    按n向下找
输入:wq           保存修改并退出
apache的配置文件在conf/httpd.conf 通常修改影响压力测试的最大和最小进程数

***********************用户和组管*******************

useradd

添加系统用户

格式:useradd [选项] <用户名>

useradd -d /*制定用户的宿主目录*/

useradd -e /*指定用户的账号失效时间,可使用YYYY-MM-DD的日期格式*/

useradd -g /*指定用户的基本组名,也可以使用GID*/

useradd -G /*指定用户的公共组名,也可以使用GID*/

useradd -M /*不为用户建立并初始化宿主目录*/

useradd -s /*指定用户的登陆shell环境*/

useradd -u /*指定用户的UID号*/

passwd

设置系统用户密码,及锁定解锁用户帐户,若为指定用户,默认设置当前用户密码

格式:passwd [选项] <用户名>

passwd -d /*清空指定用户密码*/

passwd -l /*锁定指定用户账户*/

passwd -S /*查看指定用户状态*/

passwd -u /*解锁指定用户账户*/

usermod

修改指定用户帐户信息,大部分选项与useradd命令中的相同,不再赘余!可参考useradd选项设置

格式:usermod [选项] <用户名>

userdel

删除指定用户帐户

格式:userdel [-r] <用户名>

userdel -r /*删除用户后,也将该用户的宿主目录一并删除*/

groupadd

添加一个系统用户组

格式:groupadd [-g] <组名>

groupadd -g /*为新建的组指定GID组标记*/

groupdel

删除一个系统用户组

格式:groupdel  <组名>

su

切换为另一个用户身份,不指定参数时默认切换到root用户

格是:su [-l] [目标用户名]

su -l /*使用目标用户的登陆shell环境,该选项可简写为”-”

id

输出指定用户的身份标记信息,省略用户名参数时则输出当前用户的信息

格式:id [选项] <用户名>

id -u /*只显示有效用户信息*/

id -g /*只显示有效组信息*/

id -n /*只输出用户名称*/

 

 

/*************************查看日志常用*************************/

1、more命令2、cat命令3:tac命令,倒序显示4、head命令,可以指定显示那些内容5、tali命令,可以指定显示那些内容6、less 与 more 类似,但是比 more 更好的是,他可以[pg dn][pg up]翻页!

1、more 是我们最常用的工具之一,最常用的就是显示输出的内容,然后根据窗口的大小进行分页显示,然后还能提示文件的百分比;

# more /etc/profile

more 的语法、参数和命令;

more [参数选项] [文件]

参数如下:
+num                    从第num行开始显示;
-num                    定义屏幕大小,为num行;
+/pattern       从pattern 前两行开始显示;
-c                      从顶部清屏然后显示;
-d                      提示Press space to continue, ‘ q ‘ to quit.(按空 键继续,按q键退出),禁用响铃功能;
-l                      忽略Ctrl+l (换页)字符;
-p                      通过清除窗口而不是滚屏来对文件进行换页。和-c参数有点相似;
-s                              把连续的多个空行显示为一行;
-u                              把文件内容中的下划线去掉

退出more的动作指令是q

more 的参数应用举例;

# more -dc /etc/profile    注:显示提示,并从终端或控制台顶部显示;
# more + 4 /etc/profile      注:从profile的第4行开始显示;
# more – 4 /etc/profile      注:每屏显示4行;
# more +/MAIL /etc/profile     注:从profile中的第一个MAIL单词的前两行开始显示;

more 的动作指令;

我们查看一个内容较大的文件时,要用到more的动作指令,比如ctrl+f(或空格键)是向下显示一屏,ctrl+b是返回上一屏; Enter键可以向下滚动显示n行,要通过定,默认为1行;

我们只说几个常用的;自己尝试一下就知道了;

Enter         向下n行,需要定义,默认为1行;
Ctrl+f                  向下滚动一屏;
空 键                       向下滚动一屏;
Ctrl+b          返回上一屏;
=                               输出当前行的行号;
:f                      输出文件名和当前行的行号;
v                               调用vi编辑器;
! 命令                        调用Shell,并执行命令;
q                               退出more

当我们查看某一文件时,想调用vi来编辑它,不要忘记了v动作指令,这是比较方便的;

其它命令通过管道和more结合的运用例子;

比如我们列一个目录下的文件,由于内容太多,我们应该学会用more来分页显示。这得和管道 | 结合起来,比如:

# ls -l /etc  |more

2、cat命令

使用方式:cat [-AbeEnstTuv] [–help] [–version] fileName
说明:把档案串连接后传到基本输出(萤幕或加 > fileName 到另一个档案)
参数:
-n 或 –number 由 1 开始对所有输出的行数编号
-b 或 –number-nonblank 和 -n 相似,只不过对于空白行不编号
-s 或 –squeeze-blank 当遇到有连续两行以上的空白行,就代换为一行的空白行
-v 或 –show-nonprinting
范例:
cat -n textfile1 > textfile2 把 textfile1 的档案内容加上行号后输入 textfile2 这个档案里
cat -b textfile1 textfile2 >> textfile3 把 textfile1 和 textfile2 的档案内容加上行号(空白行不加)之后将内容附加到 textfile3 里。
范例:
把 textfile1 的档案内容加上行号后输入 textfile2 这个档案里
cat -n textfile1 > textfile2
把 textfile1 和 textfile2 的档案内容加上行号(空白行不加)之后将内容附加到 textfile3 里。
cat -b textfile1 textfile2 >> textfile3
cat /dev/ null > /etc/test.txt 此为清空/etc/test.txt档案内容
cat 也可以用来制作 image file。例如要制作软碟的 image file,将软碟放好后打
cat /dev/fd0 > OUTFILE

3:tac命令,倒序显示

tac: 从最后一行开始显示,可以看出 tac 是 cat 的反向显示!

4、head命令,可以指定显示那些内容

语法:

[root @test /root ]# head [-n number] [檔名]
参数说明:
-n :显示 number 行

说明:
head 的英文意思就是『头』啦,那么这个东东的用法自然就是显示出一个档案的前几行啰!没错!就是这样!若不加参数就默认输出前面十行内容,不信自己操作一下,也可以自定义输出的行数 那就加入『 head -n number filename 』即可!
比如我们显示/etc/profile的前10行内容,应该是:# head -n 10 /etc/profile

5、tali命令,可以指定显示那些内容

tail 是显示一个文件的内容的后多少行;

用法比较简单;

tail   -n  行数值  文件名;

比如我们显示/etc/profile的最后5行内容,应该是:

# tail  -n 5 /etc/profile

6、less 与 more 类似,但是比 more 更好的是,他可以[pg dn][pg up]翻页!

more ,less

其实这两个命令有极大的相似之处都是分页显示档案内容,但是区别也是有的,如下:

1)more:以百分比的形式分页显示,提示给用户已经显示了多少内容

less:没有百分比的提示

2)less更加灵活,可用通过【page down】【page up】上翻下翻页查看已经显示出的内容,而more不具备

3)对less显示出的内容中可以使用 / ‘ 字符 ‘ 输入需要查找的字符或者字符串并高亮显示,而more 不具备

3. nl

显示档案内容时输出行号,跟cat -n类似的功能,也是全盘输出

 

more & less 用 cat 的確可以顯示一個檔案的的內容﹐但如果檔案很長﹐超過一個熒幕怎麼辦﹖

您可以按著 Shift 鍵﹐再配合 PageUP 和 PageDown 鍵來回翻閱熒幕。但畢竟有限和不方便﹐您最好用 more 命令看一個檔案﹐這樣﹐結果只顯示一個熒幕的內容﹐然後您按 Enter 鍵的話﹐則往下捲動一行﹔如果按空白鍵的話﹐則往下捲動一個熒幕。同時﹐會在最底下告訴您目前所顯示的百分比。當檔案顯示到最後一行的時候﹐命令就結束。

不過﹐用 more 命令卻不能往上翻頁哦﹐如果您想要在顯示檔案的時候能夠上下來回翻頁﹐那就用 less 命令吧。這樣﹐您按 Enter 或向下方向鍵﹐則往下捲動一行﹔按空白鍵或 PageDown 則往下捲動一個熒幕﹔按向上方向鍵則往上捲動一行﹔按 PageUp 則往上捲動一個熒幕。但是﹐用 less 命令就算檔案顯示最後一行了﹐也不會自動結束﹐您得按 Q 鍵才能離開。

head & tail 好了﹐您用 more 或 less 可以逐頁翻開一個長檔案﹐但有時候您只想看檔案的前面數行﹐而不管後面的內容是什麼﹐那您可以用 head 這個命令。預設上﹐head 只會顯示一個檔案的前 10 行﹐但您可以用 -number 來指定顯示的行數﹕

head -20 /var/log/boot.log

那就可以看到這個檔案的前 20 行內容。但反過來﹐您只想看這個檔案的最後 20 行呢﹖用 tail 命令就是了﹕

tail -20 /var/log/boot.log

和 head 一樣﹐如果您不指定顯示行數﹐那麼 tail 也只顯示檔案的最後 10 行內容。不過﹐tail 還有一個很好用的參數﹐您可以用+number 來顯示第幾行起至檔案結尾的內容﹕

tail +2 /var/log/boot.log

那麼﹐除了第一行之外﹐第 2 行後面的內容都會顯示出來。其實﹐可用的選項很多啦﹐請您自己慢慢發掘囉。

tac 仔細點看﹐這個命令 cat 是調過來寫的﹐那麼它執行結果是否也調過來呢﹖猜對了﹗用 cat 命令可以顯示一個檔案內容﹐而且按每一行的排列順序的先後顯示。而這個 tac 呢﹐也是顯示檔案內容﹐但每一行的排列順序卻是調過來的。也就是﹐最後一行最先顯示﹐最先一行最後顯示。就醬子。

 

关机:

在linux下一些常用的关机/重启命令有shutdown、halt、reboot、及init,它们都可以达到重启系统的目的,但每个命令的内部工作过程是不同的。 1.shutdown
shutdown命令安全地将系统关机。 有些用户会使用直接断掉电源的方式来关闭linux,这是十分危险的。因为linux与windows不同,其后台运行着许多进程,所以强制关机可能会导致进程的数据丢失﹐使系统处于不稳定的状态﹐甚至在有的系统中会损坏硬件设备。

而在系统关机前使用shutdown命令﹐系统管理员会通知所有登录的用户系统将要关闭。并且login指令会被冻结﹐即新的用户不能再登录。直接关机或者延迟一定的时间才关机都是可能的﹐还可能重启。这是由所有进程〔process〕都会收到系统所送达的信号〔signal〕决定的。这让像vi之类的程序有时间储存目前正在编辑的文档﹐而像处理邮件〔mail〕和新闻〔news〕的程序则可以正常地离开等等。

shutdown执行它的工作是送信号〔signal〕给init程序﹐要求它改变runlevel。Runlevel 0被用来停机〔halt〕﹐runlevel 6是用来重新激活〔reboot〕系统﹐而runlevel 1则是被用来让系统进入管理工作可以进行的状态﹔这是预设的﹐假定没有-h也没有-r参数给shutdown。要想了解在停机〔halt〕或者重新开机〔reboot〕过程中做了哪些动作﹐你可以在这个文件/etc/inittab里看到这些runlevels相关的资料。

shutdown 参数说明:
[-t] 在改变到其它runlevel之前﹐告诉init多久以后关机。
[-r] 重启计算器。
[-k] 并不真正关机﹐只是送警告信号给每位登录者〔login〕。
[-h] 关机后关闭电源〔halt〕。
[-n] 不用init﹐而是自己来关机。不鼓励使用这个选项﹐而且该选项所产生的后果往往不总是你所预期得到的。
[-c] cancel current process取消目前正在执行的关机程序。所以这个选项当然没有时间参数﹐但是可以输入一个用来解释的讯息﹐而这信息将会送到每位使用者。
[-f] 在重启计算器〔reboot〕时忽略fsck。
[-F] 在重启计算器〔reboot〕时强迫fsck。
[-time] 设定关机〔shutdown〕前的时间。

2.halt—-最简单的关机命令

其实halt就是调用shutdown -h。halt执行时﹐杀死应用进程﹐执行sync系统调用﹐文件系统写操作完成后就会停止内核。
参数说明:
[-n] 防止sync系统调用﹐它用在用fsck修补根分区之后﹐以阻止内核用老版本的超级块〔superblock〕覆盖修补过的超级块。
[-w] 并不是真正的重启或关机﹐只是写wtmp〔/var/log/wtmp〕纪录。
[-d] 不写wtmp纪录〔已包含在选项[-n]中〕。
[-f] 没有调用shutdown而强制关机或重启。
[-i] 关机〔或重启〕前﹐关掉所有的网络接口。
[-p] 该选项为缺省选项。就是关机时调用poweroff。

3.reboot
reboot的工作过程差不多跟halt一样﹐不过它是引发主机重启﹐而halt是关机。它的参数与halt相差不多。

4.init
init是所有进程的祖先﹐它的进程号始终为1﹐所以发送TERM信号给init会终止所有的用户进程﹑守护进程等。shutdown 就是使用这种机制。init定义了8个运行级别(runlevel), init 0为关机﹐init 1为重启。关于init可以长篇大论﹐这里就不再叙述。另外还有 telinit命令可以改变init的运行级别﹐比如﹐telinit -iS可使系统进入单用户模式﹐并且得不到使用shutdown时的信息和等待时间。

java内存管理及调用函数参数的传递机制

用来保存变量的值和类的实例(堆区对象的引用(指针),Person p = new Person(),p放在栈上,p是个引用(指针),new出来的对象放在堆上),常量池也放在上。【栈保存变量和引用,堆保存new出来的实例和常量池

class Test
{
public static void main (String[] args) throws java.lang.Exception
{
Test test = new Test();
int a = 10;
test.setNum(a);
System.out.println(a);
}
public void setNum(int i){
i = i+1000;
}
}

对于函数调用,只有传进去一个对象,在函数里面对这个对象执行set方法时,这个对象的值才会变,所以上面例子依旧输出10。

Java程序运行时内存分配,主要是两种类型的变量:基本类型引用类型。二者作为局部变量,都放在中,基本类型直接在栈中保存值,引用类型只保存一个指向堆区的指针,真正的对象在里。作为参数时基本类型就直接传值,引用类型传指针。

  • 基本类型有:byte、short、char、int、long、boolean。
  • 基本类型的包装类分别是:Byte、Short、Character、Integer、Long、Boolean。

上边这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。String类型也实现了常量池技术。(实现常量池技术的)

栈中的数据可以共享,所以基本类型的变量保存在栈上,当重新定义一个值相同的变量时,不会再申请一块空间,而是直接指向那个变量。比如定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。

Integer是包装类。由于Integer 包装类实现了常量池技术,因此new Integer(40)的40均是从常量池中获取的,均指向同一个地址。但是仅限[-128至127]这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取,那时候new两个变量值就不会相等了。

今天学习Python,提到python是通过赋值传递的。然后联想到Java,java传递参数是通过什么方式呢?是值传递还是引用传递?

在网上查阅资料知:java传递参数只有值传递。当参数为普通类型时,传递的是值;当参数为引用类型时,传递的是对象的引用(即地址),都是复制的值。在开发中也遇到过类似的问题:最常见的翻页问题:前台传过来一个Page对象,我们后台通过调用函数(参数中含有Page),在函数内部对page进行处理,设置Page的页数,及行数,当处理完后,函数调用结束,我们不必再将Page返回,因为此时前台传来的page对象中的值已经改变了。    其中就是因为java的值传递原理。

这里面对java值传递讲解的非常清楚:(真是一篇好文章)

  http://www.cnblogs.com/lixiaolun/p/4311775.html

捎带看了下面一片文章,又了解了一些JVM内存放面的设计:对象池。  java的基本类型的封装类有对象池的设计,如Integer类型:有cache,在-128-127范围内有赋值时,会先从对象池中取数据,此时得到的是同一个对象的引用,对象地址是相同的。当超过这个范围时,则重新创建对象。

http://www.cnblogs.com/DreamSea/archive/2011/11/20/2256396.html

在大神的博文:http://blog.csdn.NET/yangyuankp/article/details/7651251 的基础上加以修改。

本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。

进入正题前首先要知道的是Java程序运行在JVM(Java  Virtual Machine,Java虚拟机)上,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM实现了Java的平台无关性,由此可见JVM的重要性。

*.java文件首先使用javac编译成*.class文件,*.class文件是与平台无关的字节码。只要在不同的平台上实现相应的虚拟机,编译后的字节码*.class文件就可以在该平台上运行了。这是java跨平台的关键。

JVM是一个抽象的计算机,和实际的计算机一样,也有自己的指令集并使用不同的存储区域。它负责执行指令,管理数据、内存和寄存器。所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是内存分配原理的基础与前提。

 

简单通俗的讲,一个完整的Java程序运行过程会涉及以下内存区域:

寄存器:JVM内部虚拟寄存器,存取速度非常快,程序不可控制。

栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。

堆:用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。

假设有个Person类。代码:Person p = new Person();创建了一个Person对象,并把Person对象赋给p变量。这段代码产生了2个东西:

1是变量p。存放在栈中,如下图左侧。(引用类型变量)

2是Person对象。存放在堆中,如下图右侧。

常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中。

代码段:用来存放从硬盘上读取的源程序代码。

数据段:用来存放static定义的静态成员。

下面是内存表示图:

 

上图中大致描述了Java内存分配,接下来通过实例详细讲解Java程序是如何在内存中运行的(注:以下图片引用自尚学堂马士兵老师的J2SE课件,图右侧是程序代码,左侧是内存分配示意图,我会一一加上注释)。

预备知识:

1.一个Java文件,只要有main入口方法,我们就认为这是一个Java程序,可以单独编译运行。

2.无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。

示例:

 

1.JVM自动寻找main方法,执行第一句代码,创建一个Test类的实例,在栈中分配一块内存,存放一个指向堆区对象的指针110925。

2.创建一个int型的变量date,由于是基本类型,直接在栈中存放date对应的值9。

3.创建两个BirthDate类的实例d1、d2,在栈中分别存放了对应的指针指向各自的对象(对象在堆中)。他们在实例化时调用了有参数的构造方法,因此对象中有自定义初始值。

调用test对象的change1方法,并且以date为参数。JVM读到这段代码时,检测到i是局部变量,因此会把i放在栈中,并且把date的值赋给i。

把1234赋给i。很简单的一步。

change1方法执行完毕,立即释放局部变量i所占用的栈空间。

         调用test对象的change2方法,以实例d1为参数。JVM检测到change2方法中的b参数为局部变量,立即加入到栈中,由于是引用类型的变量,所以b中保存的是d1中的指针,此时b和d1指向同一个堆中的对象。在b和d1之间传递是指针。

         change2方法中又实例化了一个BirthDate对象,并且赋给b。在内部执行过程是:在堆区new了一个对象,并且把该对象的指针保存在栈中的b对应空间,此时实例b不再指向实例d1所指向的对象,但是实例d1所指向的对象并无变化,这样无法对d1造成任何影响。

         change2方法执行完毕,立即释放局部引用变量b所占的栈空间,注意只是释放了栈空间,堆空间要等待自动回收。

         调用test实例的change3方法,以实例d2为参数。同理,JVM会在栈中为局部引用变量b分配空间,并且把d2中的指针存放在b中,此时d2和b指向同一个对象。再调用实例b的setDay方法,其实就是调用d2指向的对象的setDay方法。

         调用实例b的setDay方法会影响d2,因为二者指向的是同一个对象。

         change3方法执行完毕,立即释放局部引用变量b。

以上就是Java程序运行时内存分配的大致情况。其实也没什么,掌握了思想就很简单了。无非就是两种类型的变量:基本类型和引用类型。二者作为局部变量,都放在栈中,基本类型直接在栈中保存值,引用类型只保存一个指向堆区的指针,真正的对象在堆里。作为参数时基本类型就直接传值,引用类型传指针。

小结:

1.分清什么是实例什么是对象。Class a= new Class();此时a叫实例,而不能说a是对象(new Class()才是对象)。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

2.栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。

3.以上的栈、堆、代码段、数据段等等都是相对于应用程序而言的。每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,互不影响。并且这些内存区域是所有线程共享的。这里提到的栈和堆都是整体上的概念,这些堆栈还可以细分。

4.类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。

以上分析只涉及了栈和堆,还有一个非常重要的内存区域:常量池,这个地方往往出现一些莫名其妙的问题。常量池是干嘛的上边已经说明了,也没必要理解多么深刻,只要记住它维护了一个已加载类的常量就可以了。接下来结合一些例子说明常量池的特性。

预备知识:

基本类型和基本类型的包装类。

基本类型有:byte、short、char、int、long、boolean。

基本类型的包装类分别是:Byte、Short、Character、Integer、Long、Boolean。

注意区分大小写。

二者的区别是:基本类型体现在程序中是普通变量,基本类型的包装类是类,体现在程序中是引用变量。

因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中。上边提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。另外,String类型也实现了常量池技术。

实例:

 

结果:

1
2
3
4
5
6
7
i=i0       true
i1=i2      true
i1=i2+i3   true
i4=i5      <span style="color: #ff0000;">false</span>
i4=i5+i6   true
d1=d2      <span style="color: #ff0000;">false
</span>

结果分析:

1.i和i0均是普通类型(int)的变量,所以数据直接存储在栈中,而栈有一个很重要的特性:栈中的数据可以共享。当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。

2.i1和i2均是引用类型,在栈中存储指针,因为Integer是包装类。由于Integer 包装类实现了常量池技术,因此i1、i2的40均是从常量池中获取的,均指向同一个地址,因此i1=12。

3.很明显这是一个加法运算,Java的数学运算都是在栈中进行的,Java会自动对i1、i2进行拆箱操作转化成整型,因此i1在数值上等于i2+i3。

4.i4和i5 均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i4和i5不相等,因为他们所存指针不同,所指向对象不同。

5.这也是一个加法运算,和3同理。

6.d1和d2均是引用类型,在栈中存储指针,因为Double是包装类。但Double包装类没有实现常量池技术,因此Doubled1=1.0;相当于Double d1=new Double(1.0);,是从堆new一个对象,d2同理。因此d1和d2存放的指针不同,指向的对象不同,所以不相等。

 

小结:

1.以上提到的几种基本类型包装类均实现了常量池技术,但他们维护的常量仅仅是【-128至127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。比如,把上边例子改成Integer i1 = 400; Integer i2 = 400;,很明显超过了127,无法从常量池获取常量,就要从堆中new新的Integer对象,这时i1和i2就不相等了。

2.String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。

凡是涉及内存原理,一般都是博大精深的领域,切勿听信一家之言,多读些文章。我在这只是浅析,里边还有很多猫腻,就留给读者探索思考了。希望本文能对大家有所帮助!

 

脚注:

(1) 符号引用,顾名思义,就是一个符号,符号引用被使用的时候,才会解析这个符号。如果熟悉Linux或unix系统的,可以把这个符号引用看作一个文件的软链接,当使用这个软连接的时候,才会真正解析它,展开它找到实际的文件对于符号引用,在类加载层面上讨论比较多,源码级别只是一个形式上的讨论。

当一个类被加载时,该类所用到的别的类的符号引用都会保存在常量池,实际代码执行的时候,首次遇到某个别的类时,JVM会对常量池的该类的符号引用展开,转为直接引用,这样下次再遇到同样的类型时,JVM就不再解析,而直接使用这个已经被解析过的直接引用。

除了上述的类加载过程的符号引用说法,对于源码级别来说,就是依照引用的解析过程来区别代码中某些数据属于符号引用还是直接引用,如,System.out.println(“test” +”abc”);//这里发生的效果相当于直接引用,而假设某个Strings = “abc”; System.out.println(“test” + s);//这里的发生的效果相当于符号引用,即把s展开解析,也就相当于s是”abc”的一个符号链接,也就是说在编译的时候,class文件并没有直接展看s,而把这个s看作一个符号,在实际的代码执行时,才会展开这个。

 

参考文章:

java内存分配研究:http://www.blogjava.net/Jack2007/archive/2008/05/21/202018.html

Java常量池详解之一道比较蛋疼的面试题:http://www.cnblogs.com/DreamSea/archive/2011/11/20/2256396.html

jvm常量池:http://www.cnblogs.com/wenfeng762/archive/2011/08/14/2137820.html

深入Java核心 Java内存分配原理精讲:http://developer.51cto.com/art/201009/225071.htm

Java线上应用故障排查之二:高内存占用

前一篇介绍了线上应用故障排查之一:高CPU占用,这篇主要分析高内存占用故障的排查。

Java开发的,经常会碰到下面两种异常:

1、java.lang.OutOfMemoryError: PermGen space

2、java.lang.OutOfMemoryError: Java heap space

要详细解释这两种异常,需要简单重提下Java内存模型。

(友情提示:本博文章欢迎转载,但请注明出处:hankchen,http://www.blogjava.net/hankchen

Java内存模型是描述Java程序中各变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节。

在Java虚拟机中,内存分为三个代:新生代(New)、老生代(Old)、永久代(Perm)。

(1)新生代New:新建的对象都存放这里

(2)老生代Old:存放从新生代New中迁移过来的生命周期较久的对象。新生代New和老生代Old共同组成了堆内存。

(3)永久代Perm:是非堆内存的组成部分。主要存放加载的Class类级对象如class本身,method,field等等。

如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:

(1)Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。

(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。

如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。

一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。

 

从代码的角度,软件开发人员主要关注java.lang.OutOfMemoryError: Java heap space异常,减少不必要的对象创建,同时避免内存泄漏。

现在以一个实际的例子分析内存占用的故障排查。

2G19({7(0}N(FIL09LH175N

通过top命令,发现PID为9004的Java进程一直占用比较高的内存不释放(24.7%),出现高内存占用的故障。

想起上一篇线上应用故障排查之一:高CPU占用介绍的PS命令,能否找到具体是哪个的线程呢?

ps -mp 9004 -o THREAD,tid,time,rss,size,%mem

1

遗憾的是,发现PS命令可以查到具体进程的CPU占用情况,但是不能查到一个进程下具体线程的内存占用情况。

 

只好寻求其他方法了,幸好Java提供了一个很好的内存监控工具:jmap命令

jmap命令有下面几种常用的用法:

•jmap [pid]

•jmap -histo:live [pid] >a.log

•jmap -dump:live,format=b,file=xxx.xxx [pid]

用得最多是后面两个。其中,jmap -histo:live [pid] 可以查看当前Java进程创建的活跃对象数目和占用内存大小。

jmap -dump:live,format=b,file=xxx.xxx [pid] 则可以将当前Java进程的内存占用情况导出来,方便用专门的内存分析工具(例如:MAT)来分析。

这个命令对于分析是否有内存泄漏很有帮助。具体怎么使用可以查看本博的另一篇文章:利用Eclipse Memory Analyzer Tool(MAT)分析内存泄漏

 

这里详细介绍下jmap -histo:live [pid] 命令:

1

从上图可以看出,int数组、constMethodKlass、methodKlass、constantPoolKlass都占用了大量的内存。

特别是占用了大量内存的int数组,需要仔细检查相关代码。

其中:

[C is a char[]
[S is a short[]
[I is a int[]
[B is a byte[]
[[I is a int[][]

[C对象占用Heap多,往往跟String有关,String其内部使用final char[]数组来保存数据的。

constMethodKlass/ methodKlass/ constantPoolKlass/ constantPoolCacheKlass/ instanceKlassKlass/ methodDataKlass

与Classloader相关,常驻与Perm区。

最后,总结下排查内存故障的方法和技巧有哪些:

1、top命令:Linux命令。可以查看实时的内存使用情况。

2、jmap -histo:live [pid],然后分析具体的对象数目和占用内存大小,从而定位代码。

3、jmap -dump:live,format=b,file=xxx.xxx [pid],然后利用MAT工具分析是否存在内存泄漏等等。

(友情提示:本博文章欢迎转载,但请注明出处:hankchen,http://www.blogjava.net/hankchen

线上应用故障排查之一:高CPU占用

一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环。

(友情提示:本博文章欢迎转载,但请注明出处:hankchen,http://www.blogjava.net/hankchen

以我们最近出现的一个实际故障为例,介绍怎么定位和解决这类问题。

clip_image002

根据top命令,发现PID为28555的Java进程占用CPU高达200%,出现故障。

通过ps aux | grep PID命令,可以进一步确定是tomcat进程出现了问题。但是,怎么定位到具体线程或者代码呢?

首先显示线程列表:

ps -mp pid -o THREAD,tid,time

1

找到了耗时最高的线程28802,占用CPU时间快两个小时了!

其次将需要的线程ID转换为16进制格式:

printf “%x\n” tid

2

最后打印线程的堆栈信息:

jstack pid |grep tid -A 30

3

找到出现问题的代码了!

现在来分析下具体的代码:ShortSocketIO.readBytes(ShortSocketIO.java:106)

ShortSocketIO是应用封装的一个用短连接Socket通信的工具类。readBytes函数的代码如下:

public byte[] readBytes(int length) throws IOException {

if ((this.socket == null) || (!this.socket.isConnected())) {

throw new IOException(“++++ attempting to read from closed socket”);

}

byte[] result = null;

ByteArrayOutputStream bos = new ByteArrayOutputStream();

if (this.recIndex >= length) {

bos.write(this.recBuf, 0, length);

byte[] newBuf = new byte[this.recBufSize];

if (this.recIndex > length) {

System.arraycopy(this.recBuf, length, newBuf, 0, this.recIndex – length);

}

this.recBuf = newBuf;

this.recIndex -= length;

} else {

int totalread = length;

if (this.recIndex > 0) {

totalread -= this.recIndex;

bos.write(this.recBuf, 0, this.recIndex);

this.recBuf = new byte[this.recBufSize];

this.recIndex = 0;

}

int readCount = 0;

while (totalread > 0) {

if ((readCount = this.in.read(this.recBuf)) > 0) {

if (totalread > readCount) {

bos.write(this.recBuf, 0, readCount);

this.recBuf = new byte[this.recBufSize];

this.recIndex = 0;

} else {

bos.write(this.recBuf, 0, totalread);

byte[] newBuf = new byte[this.recBufSize];

System.arraycopy(this.recBuf, totalread, newBuf, 0, readCount – totalread);

this.recBuf = newBuf;

this.recIndex = (readCount – totalread);

}

totalread -= readCount;

}

}

}

问题就出在标红的代码部分。如果this.in.read()返回的数据小于等于0时,循环就一直进行下去了。而这种情况在网络拥塞的时候是可能发生的。

至于具体怎么修改就看业务逻辑应该怎么对待这种特殊情况了。

 

最后,总结下排查CPU故障的方法和技巧有哪些:

1、top命令:Linux命令。可以查看实时的CPU使用情况。也可以查看最近一段时间的CPU使用情况。

2、PS命令:Linux命令。强大的进程状态监控命令。可以查看进程以及进程中线程的当前CPU使用情况。属于当前状态的采样数据。

3、jstack:Java提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。

4、pstack:Linux命令。可以查看某个进程的当前线程栈运行情况。

(友情提示:本博文章欢迎转载,但请注明出处:hankchen,http://www.blogjava.net/hankchen

mongostat

  • inserts/s 每秒插入次数
  • query/s 每秒查询次数

注:10条简单的查询可能比一条复杂的查询速度还快, 所以数值的大小,意义并不大。

  • update/s 每秒更新次数
  • delete/s 每秒删除次数
  • getmore/s查询时游标(cursor)的getmore操作,每秒执行getmore次数
  • command/s 每秒的命令数,比以上插入、查找、更新、删除的综合还多,还统计了别的命令,在主从系统中,会显示两个值 (例如:80|0),分别代表 本地|复制 命令的个数
  • flushs/s 每秒执行fsync将数据写入硬盘的次数。

注:一般都是0,或者1,通过计算两个1之间的间隔时间,可以大致了解多长时间flush一次。flush开销是很大的,如果频繁的flush,可能就要找找原因了。

  • mapped/s 所有的被mmap的数据量,单位是MB,
  • vsize 虚拟内存使用量,单位MB
  • res 物理内存使用量,单位MB

注:mapped, vsize一般不会有大的变动, res会慢慢的上升,如果res经常突然下降,去查查是否有别的程序狂吃内存。

  • faults/s 每秒访问失败数(只有Linux有),数据被交换出物理内存,放到swap。不要超过100,否则就是机器内存太小,造成频繁swap写入。此时要升级内存或者扩展,大压力下这个数值往往不为0。如果经常不为0,那就该加内存了。
  • locked % 被锁的时间百分比,尽量控制在50%以下吧
  • idx miss % 索引不命中所占百分比。如果太高的话就要考虑索引是不是少了
  • qr  客户端等待从 MongoDB 实例读取数据的队列长度。
  • qw  客户端等待向 MongoDB 实例写入数据的队列长度。
  • ar  执行读取操作的活动客户端的数目。
  • aw  执行写入操作的活动客户端的数目。
  • q t|r|w 当Mongodb接收到太多的命令而数据库被锁住无法执行完成,它会将命令加入队列。这一栏显示了总共、读、写3个队列的长度,都为0的话表示mongo毫无压力。高并发时,一般队列值会升高。
  • netIn
  • netOut 网络带宽压力
  • conn 当前连接数

注: MongoDB为每一个连接创建一个线程,线程的创建和释放也是有开销的。尽量不要让这个数值很大

  • time 时间戳

Web通信

为达到实时通信,一类是基于HTTP的Comet推送技术,另一类是基于套接口(Socket)传送信息实现消息传输。

一、目前使用Comet主要有两种方式,轮询和iframe流。

  • 轮询 polling
    浏览器周期性的发出请求,如果服务器没有新数据需要发送就返回以空响应。这种方法问题很大:首先,大量无意义的请求造成网络压力;其次,请求周期的限制不能及时地获得最新数据。这种方法很快就被淘汰。
  • 长轮询 long polling
    长轮询是在打开一条连接以后保持连接,等待服务器推送来数据再关闭连接。然后浏览器再发出新的请求,这能更好地管理请求数量,也能及时地更新数据。AJAX调用XMLHttpRequest对象发出HTTP请求,JS响应处理函数根据服务器返回的数据更新HTML页面的展示。这个方法一定程度上消除了简单轮询的弊端,但服务器压力也是很大。
  • iframe流 iframe streaming
    iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间建立一条长链接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。”iframe是很早就存在的一种 HTML 标记,通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。”其不足为:进度条会显示一直,反应在页面上就是浏览器标签页的图标会不停地转动。(当然这也是有解决方法的)

    二、基于WebSocket

    HTML5提供的Websocket不同于上面这些在老的HTML已有框架内的方法,而是在单个TCP连接上进行全双工通讯的协议。目前主流浏览器都已支持。
    1. 初始化过程
    不同于早期JAVA使用在浏览器安装插件的方法——-Java Applet 套接口:这种方法不足在于Java Applet再收到服务器返回的消息后,无法通过Javascript去更新HTML页面的内容。而是通过HTTP建立连接(HTTP handshake)。
    2. 开始通讯
    一旦初始连接建立,浏览器和服务器就打开了一个TCP socket的频道。在这个频道内就能进行双向的数据通信。

    然而Websocket依然有一些问题。比如浏览器兼容性问题(随着浏览器的发展,肯定是越来越小的),以及网络中间物(代理服务、防火墙)问题不支持WebSocket,这时Socket.io的出现就是为了完善WebSocket。

  • Socket.IO

    Guillermo Rauch在2010年开发第一版时,目的很明确地指向Node.js实时应用。在几次版本更新后,重新定义和封装核心功能而分化出一个基础模块 Engine.io——力求建立更稳定的工具。Engine.IO有着更稳定的连接质量。使得Socket.IO在先打开一个长轮询,再在将连接推至WebSocket频道继续通信。
    在使用Node的http模块创建服务器同时还要Express应用,因为这个服务器对象需要同时充当Express服务和Socket.io服务。

java修饰符static和final

static表示不要实例化就可以使用,静态变量只在加载类的过程中分配一次内存,没有被static修饰的实例变量,在每次实例化时都分配一次内存。

static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!
对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
对于方法,表示不可覆盖,并且可以通过类名直接访问。

修饰符
名称 说明 备注
static 静态变量(又称为类变量,其它的称为实例变量) 可以被类的所有实例共享。并不需要创建类的实例就可以访问静态变量
final 常量,值只能够分配一次,不能更改 注意不要使用const,虽然它和C、C++中的const关键字含义一样可以同static一起使用,避免对类的每个实例维护一个拷贝
transient 告诉编译器,在类对象序列化的时候,此变量不需要持久保存 主要是因为改变量可以通过其它变量来得到,使用它是为了性能的问题
volatile 指出可能有多个线程修改此变量,要求编译器优化以保证对此变量的修改能够被正确的处理

final类不能被继承,没有子类,final类中的方法默认是final的。
final方法不能被子类的方法覆盖,但可以被继承。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。

static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用(当然也可以在非静态成员方法中使用–废话),但是不能在其他类中通过类名来直接引用,这一点很重要。private是访问权限限定,static表示不要实例化就可以使用。

 

1、static变量

        静态变量
        按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。两者的区别是:
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。

2、静态方法
        静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为实例成员与特定的对象关联!这个需要去理解,想明白其中的道理,不是记忆!!!
因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
3、static代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。例如:
public class Test5 {
private static int a;
private int b;static {
Test5.a = 3;
System.out.println(a);
Test5 t = new Test5();
t.f();
t.b = 1000;
System.out.println(t.b);
}static {
Test5.a = 4;
System.out.println(a);
}public static void main(String[] args) {
// TODO 自动生成方法存根
}

static {
Test5.a = 5;
System.out.println(a);
}

public void f() {
System.out.println(“hhahhahah”);
}
}

运行结果:
3
hhahhahah
1000
4
5
        利用静态代码块可以对一些static变量进行赋值,最后再看一眼这些例子,都一个static的main方法,这样JVM在运行main方法的时候可以直接调用而不用创建实例。

MongoDB使用笔记

MongoDB安装并设置成Windows服务的教程:http://www.weste.net/2014/10-31/99742.html
使用mongod –dbpath f:/MongoDB/data 命令开启服务后才能使用mongo命令
mongod.exe –dbpath f:/MongoDB/data

插入数据:db.hy.insert({id:2016,userName:’hy’,age:21});

查询数据:db.hy.find();

设置服务:mongod –logpath F:\MongoDB\logs\mongodb.log –logappend –dbpath F:\MongoDB\data –serviceName MongoDB –install
shell操作:
1.创建:db.a.insert({“name”:”jack”,”age”:19})
db.a.insert({“name”:”luce”,”age”:20})

2.查找:db.a.find() –全查
db.a.findOne() –只取多个的第一个
db.a.find({“name”:”jack”}) –根据条件查找
db.getCollection(‘user’).find({}).limit(1)
db.getCollection(‘user’).find({}).skip(20).limit(10)
db.getCollection(‘user’).find({“$or”:[{“uname”:/辣鸡/},{“telephone”:/1314147/}]})
db.getCollection(‘user’).find({“_id”:{$in:[“11915″,”17446”]}})
db.getCollection(‘user’).find({“_id”:{$nin:[“11915″,”17446”]}})

3.更新:db.a.update({“name”:”jack”},{“age”:30})

4.删除:db.a.remove() –删除所有文档,集合保留
db.a.remove({“name”:”jack”}) –删除复合条件的文档
db.a.drop() –删除集合,于此同时所有文档也删除了
5.或操作:db.getCollection(‘tournamentMatch’).find({“$or”:[{‘playerA.uid’:’12486′},{‘playerB.uid’:’12988′}]})

6.update:db.collection.update( criteria, objNew, upsert, multi )

criteria : update的查询条件,类似sql update查询内where后面的
objNew : update的对象和一些更新的操作符(如$,$inc…)等,也可以理解为sql update查询内set后面的
upsert : 这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi : mongodb默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。

db.getCollection(“club”).update({“_id”:clubId},{$set:{“chairmanId”:newChairmanId}},false,false)

7.复制线上数据库db.copyDatabase(“laosiji”, “laosiji-server”, “123.56.187.38”, “laosiji”, “laosijI2016”, “SCRAM-SHA-1”)

8.操作符
(>) 大于 – $gt
(<) 小于 – $lt
(>=) 大于等于 – $gte
(<= ) 小于等于 – $lte、
db.col.find({“id” : {$gt : 100}})

9.多条件与、或、非操作
http://blog.csdn.net/subuser/article/details/46415739
http://www.blogjava.net/xiaomage234/archive/2012/08/06/384904.html

10.db.getCollection(‘club’).find({“name”:/哥哥.*/}) :包含哥哥这两个字
db.getCollection(‘club’).find({“name”:/^好哥哥/}) :以好哥哥开头

11.删除数据库某个字段
db.user.update({},{$unset:{“num”:0}},false,true)//可以对多条数据更新 true字段

12.重命名自段
db.user.update({}, {$rename : {“name” : “uname”}}, false, true)

13.类型转换
mongo可以通过find(…).forEach(function(x) {})语法来修改collection的field类型。
假设collection为foo,field为bad:
转换为int类型:
db.foo.find({bad: {$exists: true}}).forEach(function(obj) {
obj.user_id = new NumberInt(obj.user_id);
db.foo.save(obj);
});
同理转换为string类型:
db.foo.find( { bad : { $exist : true } } ).forEach( function (x) {
x.bad = new String(x.bad); // convert field to string
db.foo.save(x);
});
14.加索引命令
db.user.createIndex({“uname”:1})
15.判断某个字段是否存在
db.getCollection(‘signUpRecord’).find({“tourStartTime”:{$exists:true}})
16.排序
db.getCollection(‘user’).find({}).sort({“websitePoint”:-1}).limit(10)

17.脚本的// 查询写法
var matchCursor = db.match.find({“_id”:{$regex:challengeId}});

18.pull
db.getCollection(‘friends’).update({“appliedList.uid”:”17269″},{$pull:{“appliedList”:{“uid”:”17269″}}},false,true)

19.数组查询
db.getCollection(‘userHonor’).find({“honorList.0”:{$exists:1}})
db.getCollection(‘userHonor’).find({“honorList”:{$size:1}})
db.getCollection(‘userHonor’).find({$where:”this.honorList.length>0″})
db.getCollection(‘userHonor’).find({“honorList”:{$elemMatch:{“honor”:”goddess”,”level”:1}}})
$where 在走投无路的时候可以用,但它的效率是很低的。

JAVA笔记

Java笔记

Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1

一、JAVA集合主要分为三种类型:

  • Set(集)
  • List(列表)
  • Map(映射)

Collection 接口 :Collection是最基本的集合接口,声明了适用于JAVA集合(只包括Set和List)的通用方法。 Set 和List 都继承了Conllection,Map

1.Set(集合): Set是最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。 Set接口主要实现了两个实现类:

  • HashSet: HashSet类按照哈希算法来存取集合中的对象,存取速度比较快
  • TreeSet :TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序。

Set 的用法:存放的是对象的引用,没有重复对象

Set set=new HashSet();

String s1=new String("hello");

String s2=s1;

String s3=new String("world");

set.add(s1);

set.add(s2);

set.add(s3);

System.out.println(set.size());//打印集合中对象的数目 为 2。

  • HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。
  • TreeSet: 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。
  • LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。

 

2.List(列表): List的特征是其元素以线性方式存储,集合中可以存放重复对象。

List接口主要实现类包括:

  • ArrayList() : 代表长度可以改变得数组。可以对元素进行随机的访问,向ArrayList()中插入与删除元素的速度慢。
  • LinkedList(): 在实现中采用链表数据结构。插入和删除速度快,访问速度慢。

List:次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推 荐LinkedList使用。)一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元 素。

ArrayList:由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历 ArrayList,而不是用来插入和移除元素。因为那比LinkedList开销要大很多。

LinkedList :对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替。)还具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。

3.Map(映射): 

Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口 从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

总结:Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于 Set,而不关心它的顺序–否则应该使用List)。Map同样对每个元素保存一份,但这是基于”键”的,Map也有内置的排序,因而不关心元素添加的 顺序。如果添加元素的顺序对你很重要,应该使用 LinkedHashSet或者LinkedHashMap.

  • Map : 维护“键值对”的关联性,使你可以通过“键”查找“值”
  • HashMap:Map基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。
  • LinkedHashMap: 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。
  • TreeMap : 基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在 于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。
  • WeakHashMao :弱键(weak key)Map,Map中使用的对象也被允许释放: 这是为解决特殊问题设计的。如果没有map之外的引用指向某个“键”,则此“键”可以被垃圾收集器回收。
  • IdentifyHashMap: : 使用==代替equals()对“键”作比较的hash map。专为解决特殊问题而设计。

二、Java中共有8种基本数据类型,包括4 种整型、2 种浮点型、1 种字符型、1 种布尔型,请见下表。

Java基本数据类型
数据类型 说明 所占内存 举例 备注
byte 字节型 1 byte 3, 127
short 短整型 2 bytes 3, 32767
int 整型 4 bytes 3, 21474836
long 长整型 8 bytes 3L, 92233720368L long最后要有一个L字母(大小写无所谓)。
float 单精度浮点型 4 bytes 1.2F, 223.56F float最后要有一个F字母(大小写无所谓)。
double 双精度浮点型 8 bytes 1.2, 1.2D, 223.56, 223.56D double最后最好有一个D字母(大小写无所谓)。
char 字符型 2 bytes ‘a’, ‘A’ 字符型数据只能是一个字符,由单引号包围。
boolean 布尔型 1 bit true, false

 

float 类型有效数字最长为 7 位,有效数字长度包括了整数部分和小数部分。例如:

  1. floatx = 223.56F;
  2. floaty = 100.00f;

注意:每个float类型后面都有一个标志“F”或“f”,有这个标志就代表是float类型。

double 类型有效数字最长为 15 位。与 float 类型一样,double 后面也带有标志“D”或“d”。例如:

  1. doublex = 23.45D;
  2. doubley = 422.22d;
  3. doublez = 234;

注意:不带任何标志的浮点型数据,系统默认是 double 类型。

三、二进制、八进制、十六进制:

八进制有一个前缀 0,例如 010 对应十进制中的 8;十六进制有一个前缀 0x,例如 0xCAFE;从 Java 7 开始,可以使用前缀 0b 来表示二进制数据,例如 0b1001 对应十进制中的 9。同样从 Java 7 开始,可以使用下划线来分隔数字,类似英文数字写法,例如 1_000_000 表示 1,000,000,也就是一百万。下划线只是为了让代码更加易读,编译器会删除这些下划线。

四、运算符及类型转换

自动转换按从低到高的顺序转换。不同类型数据间的优先关系如下:
低———————————————>高
byte,short,char-> int -> long -> float -> double
运算中,不同类型的数据先转化为同一类型,然后进行运算,转换规则如下:

操作数1类型 操作数2类型 转换后的类型
byte、short、char int int
byte、short、char、int long long
byte、short、char、int、long float float
byte、short、char、int、long、float double double

移位:

value << num
num 指定要移位值value 移动的位数。
左移的规则只记住一点:丢弃最高位,0补最低位
如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模。如对int型移动33位,实际上只移动了33%32=1位。

五、StringBuilder类

StringBuilder类和StringBuffer类功能基本相似,方法也差不多,主要区别在于StringBuffer类的方法是多线程安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点。

StringBuffer、StringBuilder、String中都实现了CharSequence接口。

CharSequence是一个定义字符串操作的接口,它只包括length()、charAt(int index)、subSequence(int start, int end) 这几个API。

StringBuffer、StringBuilder、String对CharSequence接口的实现过程不一样,如下图所示:
图1  对CharSequence接口的实现
可见,String直接实现了CharSequence接口;StringBuilder 和 StringBuffer都是可变的字符序列,它们都继承于AbstractStringBuilder,实现了CharSequence接口。

总结

线程安全:

  • StringBuffer:线程安全
  • StringBuilder:线程不安全

速度:
一般情况下,速度从快到慢为 StringBuilder > StringBuffer > String,当然这是相对的,不是绝对的。

使用环境:

  • 操作少量的数据使用 String;
  • 单线程操作大量数据使用 StringBuilder;
  • 多线程操作大量数据使用 StringBuffer。
  • 类及实例化

局部变量:在方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。

成员变量:成员变量是定义在类中、方法体之外的变量。这种变量在创建对象的时候实例化(分配内存)。成员变量可以被类中的方法和特定类的语句访问。

类变量:类变量也声明在类中,方法体之外,但必须声明为static类型。

  • 构造方法不能被显示调用。
  • 构造方法不能有返回值,因为没有变量来接收返回值。

 

在Java中,使用new关键字来创建对象,一般有以下三个步骤:

  • 声明:声明一个对象,包括对象名称和对象类型。
  • 实例化:使用关键字new来创建一个对象。
  • 初始化:使用new创建对象时,会调用构造方法初始化对象。
  • 访问修饰符

Java中所谓的“friendly”和“default”都只是一种说法,并不是说真有那么一个指定默认访问权限的关键字。Java中类的成员权限修饰符只有三个:public/private/protected。

修饰符 说明
public 共有的,对所有类可见。
protected 受保护的,对同一包内的类和所有子类可见。
private 私有的,在同一类内可见。
默认的 在同一包内可见。默认不使用任何修饰符。

 

protected:受保护的

被声明为protected的变量、方法和构造方法能被同一个包中的任何其他类访问,也能够被不同包中的子类访问。

protected访问修饰符不能修饰类和接口,方法和成员变量能够声明为protected,但是接口的成员变量和成员方法不能声明为protected。

子类能访问protected修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。

默认的:不使用任何关键字

不使用任何修饰符声明的属性和方法,对同一个包内的类是可见的。接口里的变量都隐式声明为public static final,而接口里的方法默认情况下访问权限为public。

访问控制和继承

请注意以下方法继承(不了解继承概念的读者可以跳过这里,或者点击 Java继承和多态 预览)的规则:

父类中声明为public的方法在子类中也必须为public。

父类中声明为protected的方法在子类中要么声明为protected,要么声明为public。不能声明为private。

父类中默认修饰符声明的方法,能够在子类中声明为private。

父类中声明为private的方法,不能够被继承。

访问修饰符
名称 说明 备注
public 可以被任何类访问
protected 可以被同一包中的所有类访问

可以被所有子类访问

子类没有在同一包中也可以访问
private 只能够被 当前类的方法访问
缺省

无访问修饰符

可以被同一包中的所有类访问 如果子类没有在同一个包中,也不能访问

 

修饰符
名称 说明 备注
static 静态变量(又称为类变量,其它的称为实例变量) 可以被类的所有实例共享。

并不需要创建类的实例就可以访问静态变量

final 常量,值只能够分配一次,不能更改 注意不要使用const,虽然它和C、C++中的const关键字含义一样

可以同static一起使用,避免对类的每个实例维护一个拷贝

transient 告诉编译器,在类对象序列化的时候,此变量不需要持久保存 主要是因为改变量可以通过其它变量来得到,使用它是为了性能的问题
volatile 指出可能有多个线程修改此变量,要求编译器优化以保证对此变量的修改能够被正确的处理

 

  • 变量的作用域和this关键字

 

在Java中,变量的作用域分为四个级别:类级、对象实例级、方法级、块级。

类级变量又称全局级变量或静态变量,需要使用static关键字修饰,你可以与 C/C++ 中的 static 变量对比学习。类级变量在类定义后就已经存在,占用内存空间,可以通过类名来访问,不需要实例化。

对象实例级变量就是成员变量,实例化后才会分配内存空间,才能访问。

方法级变量就是在方法内部定义的变量,就是局部变量。

块级变量就是定义在一个块内部的变量,变量的生存周期就是这个块,出了这个块就消失了,比如 if、for 语句的块。块是指由大括号包围的代码

public class Demo{
    {
        int j = 2;// 块级变量  属性块,在类初始化属性时候运行
    }
}

this 关键字用来表示当前对象本身,this 只有在类实例化后才有意义。super 关键字与 this 类似,this 用来表示当前类的实例,super 用来表示父类。super 不是一个对象的引用,不能将 super 赋值给另一个对象变量,它只是一个指示编译器调用父类方法的特殊关键字。

匿名对象就是没有名字的对象

new B(this).print(); // 匿名对象 (没有B b = new B(),所以是匿名对象)。

  • 方法重载

同一个类中的多个方法可以有相同的名字,只要它们的参数列表不同就可以,这被称为方法重载

  • 声明为final的方法不能被重载。
  • 声明为static的方法不能被重载,但是能够被再次声明。
  • 仅仅返回类型不同不足以成为方法的重载。
  • Java类的运行顺序
  1. publicclass Demo{                                      
  2.     private String name;
  3.     private int age;
  4.     public Demo(){
  5.         name = "namestr";
  6.         age = 22;
  7.     }
  8.      public static void main(String[] args){
  9.         Demo obj = new Demo();
  10.         System.out.println(obj.name + "的年龄是" + obj.age);
  11.     }
  12. }

基本运行顺序是:

  1. 先运行到第 9 行,这是程序的入口。
  2. 然后运行到第 10 行,这里要 new 一个Demo,就要调用 Demo 的构造方法。
  3. 就运行到第 5 行,注意:可能很多人觉得接下来就应该运行第 6 行了,错!初始化一个类,必须先初始化它的属性。
  4. 因此运行到第 2 行,然后是第 3 行。
  5. 属性初始化完过后,才回到构造方法,执行里面的代码,也就是第 6 行、第 7 行。
  6. 然后是第8行,表示 new 一个Demo实例完成。
  7. 然后回到 main 方法中执行第 11 行。
  8. 然后是第 12 行,main方法执行完毕。
  9.  父类静态块
  10.  自身静态块
  11.  父类块
  12.  父类构造器
  13.  自身块
  14.  自身构造器

Java执行步骤:

父类静态(静态块和静态成员变量谁在前先执行谁)>子类静态(静态块和静态成员变量)>父类块和成员变量>父类构造器>子类块和成员变量>子类构造器>

  • Java包装类、拆箱和装箱

Java为每种基本数据类型分别设计了对应的类,称之为包装类(Wrapper Classes)。

基本数据类型及对应的包装类
基本数据类型 对应的包装类
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean
  • 由基本类型向对应的包装类转换称为装箱,例如把 int 包装成 Integer 类的对象;
  • 包装类向对应的基本类型转换称为拆箱,例如把 Integer 类的对象重新简化为 int。

1)  int 和 Integer 的相互转换

int m = 500;

Integer obj = new Integer(m); // 手动装箱

int n = obj.intValue(); // 手动拆箱

Java 1.5 之后可以自动拆箱装箱:

int m = 500;

Integer obj = m; // 自动装箱

int n = obj; // 自动拆箱

2) 将字符串转换为整数

int m = Integer.parseInt(“123”, 10);

只有是“123”这样的字符串才可以转换为整数,否则会抛出异常。

3) 将整数转换为字符串

int m = 500;

String s = Integer.toString(m);

  • Java多态和动态绑定

方法重写(覆盖):函数名相同,参数列表相同,返回值类型相同

方法重载:函数名相同,必须具有不同的参数列表(参考不同的构造方法)

父类的变量可以引用父类的实例,也可以引用子类的实例。比如Animal的变量可以引用动物的实例,也可以引用猫的实例,因为猫也是动物,但反过来不行。

多态:指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,但是如果子类重写了父类中的方法,那么调用的时候就会使用子类的这些方法。继承是子类获得父类的成员,重写是继承后重新实现父类的方法。重载是在一个类里一系列参数不同名字相同的方法。多态则是用基类的引用指向子类的对象。

动态绑定

为了理解多态的本质,下面讲一下Java调用方法的详细流程。
1) 编译器查看对象的声明类型和方法名。
假设调用 obj.func(param),obj 为 Cat 类的对象。需要注意的是,有可能存在多个名字为func但参数签名不一样的方法。例如,可能存在方法 func(int) 和 func(String)。编译器将会一一列举所有 Cat 类中名为func的方法和其父类 Animal 中访问属性为 public 且名为func的方法。

这样,编译器就获得了所有可能被调用的候选方法列表。

2) 接下来,编泽器将检查调用方法时提供的参数签名。
如果在所有名为func的方法中存在一个与提供的参数签名完全匹配的方法,那么就选择这个方法。这个过程被称为重载解析(overloading resolution)。例如,如果调用 func(“hello”),编译器会选择 func(String),而不是 func(int)。由于自动类型转换的存在,例如 int 可以转换为 double,如果没有找到与调用方法参数签名相同的方法,就进行类型转换后再继续查找,如果最终没有匹配的类型或者有多个方法与之匹配,那么编译错误。
这样,编译器就获得了需要调用的方法名字和参数签名。

3) 如果方法的修饰符是private、static、final(static和final将在后续讲解),或者是构造方法,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式 称为静态绑定(static binding)。
与此对应的是,调用的方法依赖于对象的实际类型, 并在运行时实现动态绑。例如调用 func(“hello”),编泽器将采用动态绑定的方式生成一条调用 func(String) 的指令。

4)当程序运行,并且釆用动态绑定调用方法时,JVM一定会调用与 obj 所引用对象的实际类型最合适的那个类的方法。我们已经假设 obj 的实际类型是 Cat,它是 Animal 的子类,如果 Cat 中定义了 func(String),就调用它,否则将在 Animal 类及其父类中寻找。

每次调用方法都要进行搜索,时间开销相当大,因此,JVM预先为每个类创建了一个方法表(method lable),其中列出了所有方法的名称、参数签名和所属的类。这样一来,在真正调用方法的时候,虚拟机仅查找这个表就行了。在上面的例子中,JVM 搜索 Cat 类的方法表,以便寻找与调用 func(“hello”) 相匹配的方法。这个方法既有可能是 Cat.func(String),也有可能是 Animal.func(String)。注意,如果调用super.func(“hello”),编译器将对父类的方法表迸行搜索。

假设 Animal 类包含cry()、getName()、getAge() 三个方法,那么它的方法表如下:
cry() -> Animal.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()

实际上,Animal 也有默认的父类 Object(后续会讲解),会继承 Object 的方法,所以上面列举的方法并不完整。

假设 Cat 类覆盖了 Animal 类中的 cry() 方法,并且新增了一个方法 climbTree(),那么它的参数列表为:
cry() -> Cat.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()
climbTree() -> Cat.climbTree()

在运行的时候,调用 obj.cry() 方法的过程如下:

  • JVM 首先访问 obj 的实际类型的方法表,可能是 Animal 类的方法表,也可能是 Cat 类及其子类的方法表。
  • JVM 在方法表中搜索与 cry() 匹配的方法,找到后,就知道它属于哪个类了。
  • JVM 调用该方法。

方法调用的优先问题 ,优先级由高到低依次为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。(先调用当前的方法,没有则调用父类的方法,还没有则调用当前方法中的父类参数,再没有则调用父类方法中的父类参数)

  • 多态对象的类型转换

instanceof 运算符用来判断一个变量所引用的对象的实际类型,注意是它引用的对象的类型,不是变量的类型。如果变量a引用的是当前类ClassXX或它的子类的实例,a instanceof ClassXX 返回 true,否则返回 false。

在继承链中,我们将子类向父类转换称为“向上转型”,将父类向子类转换称为“向下转型”。
很多时候,我们会将变量定义为父类的类型,却引用子类的对象,这个过程就是向上转型。

不能直接将父类的对象强制转换为子类类型,只能将向上转型后的子类对象再次转换为子类类型。也就是说,子类对象必须向上转型后,才能再向下转型。

SuperClass superObj = new SuperClass();

SonClass sonObj = new SonClass();

 

// 下面的代码运行时会抛出异常,不能将父类对象直接转换为子类类型

// SonClass sonObj2 = (SonClass)superObj;

 

// 先向上转型,再向下转型

superObj = sonObj;(此时superObj instanceof SonClass为true,因为superObj 是 SonClass 类的实例)

SonClass sonObj1 = (SonClass)superObj;

  • static关键字和final关键字

静态变量也叫类变量,静态方法也叫类方法。

实例变量只能通过对象来访问,不能通过类访问,静态变量可以通过类直接访问。

  1. static关键字修饰的属性是类级别数据,它的数据是共享的,无需创建类的实例就可以访问,且只分配一个内存空间(一个线程修改了值,其他线程访问就变了)(推荐使用类来访问:Person.id)
    非static关键字修饰的元素是对象级别的,是各自拥有一份而之间没有任何关系的。

 

关于静态变量和静态方法的总结:

  • 一个类的静态方法只能访问静态变量;
  • 一个类的静态方法不能够直接调用非静态方法;
  • 如访问控制权限允许,静态变量和静态方法也可以通过对象来访问,但是不被推荐;
  • 静态方法中不存在当前对象,因而不能使用this,当然也不能使用 super;
  • 静态方法不能被非静态方法覆盖;
  • 构造方法不允许声明为 static 的;
  • 局部变量不能使用static修饰。

静态初始器(静态块)

块是由大括号包围的一段代码。静态初始器(Static Initializer)是一个存在于类中、方法外面的静态块。静态初始器仅仅在类装载的时候(第一次使用类的时候)执行一次,往往用来初始化静态变量。

静态导入

静态导入是 Java 5 的新增特性,用来导入类的静态变量和静态方法。

import static java.lang.System.*;

    import static java.lang.Math.random;

    public class Demo {

        public static void main(String[] args) {

        out.println("产生的一个随机数:" + random());

    }

}

因为System.out是静态方法。

final关键字:

  • final 修饰的类不能被继承。
  • final 修饰的方法不能被子类重写。
  • final 修饰的变量(成员变量或局部变量)即成为常量,只能赋值一次。
  • final 修饰的成员变量必须在声明的同时赋值,如果在声明的时候没有赋值,那么只有 一次赋值的机会,而且只能在构造方法中显式赋值,然后才能使用。
  • final 修饰的局部变量可以只声明不赋值,然后再进行一次性的赋值。

final 也可以用来修饰类(放在 class 关键字前面),阻止该类再派生出子类。方法也可以被 final 修饰,被 final 修饰的方法不能被覆盖;变量也可以被 final 修饰,被 final 修饰的变量在创建对象以后就不允许改变它们的值了。一旦将一个类声明为 final,那么该类包含的方法也将被隐式地声明为 final,但是变量不是。被 final 修饰的方法为静态绑定,不会产生多态(动态绑定),被 static 或 private 修饰的方法会被隐式的声明为 final,因为动态绑定没有意义。

Java Object类:

在Java中,只有基本类型不是对象,例如数值、字符和布尔型的值都不是对象,所有的数组类型,不管是对象数组还是基本类型数组都是继承自 Object 类。

  • equals()方法只能比较引用类型,“==”可以比较引用类型及基本类型。
  • 如果两个对象相同,那么它们的 hashCode 值一定要相同;如果两个对象的 hashCode 值相同,它们并不一定相同。
  • Java内部类及其实例化

在 Java 中,允许在一个类(或方法、语句块)的内部定义另一个类,称为内部类(Inner Class),有时也称为嵌套类(Nested Class)。

使用内部类的主要原因有:

  • 内部类可以访问外部类中的数据,包括私有的数据。
  • 内部类可以对同一个包中的其他类隐藏起来。
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。
  • 减少类的命名冲突。

public class Outer {

    private int size;

    public class Inner {

        private int counter = 10;

        public void doStuff() {

            size++;

        }

    }

    public static void main(String args[]) {

        Outer outer = new Outer();

        Inner inner = outer.new Inner();//A.B b = new A().new B();

        inner.doStuff();

        System.out.println(outer.size);

        System.out.println(inner.counter);

    }

}

内部类可以是静态(static)的,可以使用 public、protected 和 private 访问控制符,而外部类只能使用 public,或者默认。

成员式内部类

在外部类内部直接定义(不在方法内部或代码块内部)的类就是成员式内部类,它可以直接使用外部类的所有变量和方法,即使是 private 的。外部类要想访问内部类的成员变量和方法,则需要通过内部类的对象来获取。

请看下面的代码:

public class Outer{

    private int size;

    public class Inner {

        public void dostuff() {

            size++;

       }

    }

    public void testTheInner() {

        Inner in = new Inner();

        in.dostuff();

    }

}

成员式内部类如同外部类的一个普通成员。

成员式内部类可以使用各种修饰符,包括 public、protected、private、static、final 和 abstract,也可以不写。

若有 static 修饰符,就为类级,否则为对象级。类级可以通过外部类直接访问,对象级需要先生成外部的对象后才能访问。

非静态内部类中不能声明任何 static 成员。

内部类可以相互调用,例如:

classA {

    // B、C 间可以互相调用

    class B {}

    class C {}

}

成员式内部类的访问

内部类的对象以成员变量的方式记录其所依赖的外层类对象的引用,因而可以找到该外层类对象并访问其成员。该成员变量是系统自动为非 static 的内部类添加的,名称约定为“outClassName.this”。

1) 使用内部类中定义的非静态变量和方法时,要先创建外部类的对象,再由“outObjectName.new”操作符创建内部类的对象,再调用内部类的方法,如下所示:

public class Demo{

    public static void main(String[] args) {

        Outer outer = new Outer();

        Outer.Inner inner = outer.new Inner();

        inner.dostuff();

    }

}

class Outer{

    private int size;

    class Inner{

        public void dostuff() {

        size++;

    }

    }

}

2) static 内部类相当于其外部类的 static 成员,它的对象与外部类对象间不存在依赖关系,因此可直接创建。示例如下:

publicclass Demo{

public static void main(String[] args) {

Outer.Inner inner = new Outer.Inner();

inner.dostuff();

}

}

  1. classOuter{
  2. private static int size;
  3. static class Inner {
  4. public void dostuff() {
  5. size++;
  6. System.out.println(“size=” + size);
  7. }
  8. }
  9. }

运行结果:
size=1

3) 由于内部类可以直接访问其外部类的成分,因此当内部类与其外部类中存在同名属性或方法时,也将导致命名冲突。所以在多层调用时要指明,如下所示:

  1. publicclass Outer{
  2. private int size;
  3. public class Inner{
  4. private int size;
  5. public void dostuff(int size){
  6. size++;  // 局部变量 size;
  7. this.size;  // 内部类的 size
  8. Outer.this.size++;  // 外部类的 size
  9. }
  10. }
  11. }

局部内部类

局部内部类(Local class)是定义在代码块中的类。它们只在定义它们的代码块中是可见的。

局部类有几个重要特性:

  1. 仅在定义了它们的代码块中是可见的;
  2. 可以使用定义它们的代码块中的任何局部final 变量;
  3. 局部类不可以是 static 的,里边也不能定义 static 成员;
  4. 局部类不可以用 public、private、protected 修饰,只能使用缺省的;
  5. 局部类可以是 abstract 的。

请看下面的代码:

  1. publicclass Outer {
  2. public static final int TOTAL_NUMBER = 5;
  3. public int id = 123;
  4. public void func() {
  5. final int age = 15;
  6. String str = “http://www.weixueyuan.net”;
  7. class Inner {
  8. public void innerTest() {
  9. System.out.println(TOTAL_NUMBER);
  10. System.out.println(id);
  11. // System.out.println(str);不合法,只能访问本地方法的final变量
  12. System.out.println(age);
  13. }
  14. }
  15. new Inner().innerTest();
  16. }
  17. public static void main(String[] args) {
  18. Outer outer = new Outer();
  19. outer.func();
  20. }
  21. }

运行结果:
5
123
15

匿名内部类

匿名内部类是局部内部类的一种特殊形式,也就是没有变量名指向这个类的实例,而且具体的类实现会写在这个内部类里面。

注意:匿名类必须继承一个父类或实现一个接口。

不使用匿名内部类来实现抽象方法:

  1. abstractclass Person {
  2. public abstract void eat();
  3. }
  4. classChild extends Person {
  5. public void eat() {
  6. System.out.println(“eat something”);
  7. }
  8. }
  9. publicclass Demo {
  10. public static void main(String[] args) {
  11. Person p = new Child();
  12. p.eat();
  13. }
  14. }

运行结果:
eat something

可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用。但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?

这个时候就引入了匿名内部类。使用匿名内部类实现:

  1. abstractclass Person {
  2. public abstract void eat();
  3. }
  4. publicclass Demo {
  5. public static void main(String[] args){
  6. // 继承 Person 类
  7. new Person() {
  8. public void eat() {
  9. System.out.println(“eat something”);
  10. }
  11. }.eat();
  12. }
  13. }

可以看到,匿名类继承了 Person 类并在大括号中实现了抽象类的方法。

内部类的语法比较复杂,实际开发中也较少用到,本教程不打算进行深入讲解,各位读者也不应该将内部类作为学习Java的重点。

  • 抽象类

如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来支撑它,那么这样的类我们称它为抽象类。比如new Animal(),我们都知道这个是产生一个动物Animal对象,但是这个Animal具体长成什么样子我们并不知道,它没有一个具体动物的概念,所以他就是一个抽象类,需要一个具体的动物,如狗、猫来对它进行特定的描述,我们才知道它长成啥样。

在自上而下的继承层次结构中,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度看,祖先类更加通用,它只包含一些最基本的成员,人们只将它作为派生其他类的基类,而不会用来创建对象。

在面向对象领域由于抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能实例化的。

只给出方法定义而不具体实现的方法被称为抽象方法,抽象方法是没有方法体的,在代码的表达上就是没有“{}”。包含一个或多个抽象方法的类也必须被声明为抽象类。
使用 abstract 修饰符来表示抽象方法和抽象类。

抽象类不能被实例化,抽象方法必须在子类中被实现。

不能有抽象构造方法或抽象静态方法。

无法从静态上下文引用静态变量。

public class TestPro {

    private String str = "1231542";

    abstract class Animal {

        //抽象类中可以有自己的方法,比如setter、getter方法

        public abstract void cry();

    }

    class Cat extends Animal {

       @Override

        public void cry() {

            System.out.println("猫叫:喵喵...");

        }

    }

    class Dog extends Animal {

        @Override

        public void cry() {

            System.out.println("狗叫:汪汪...");

        }

    }

    public static void main(String[] args) {

        TestPro.Animal a1 = new TestPro().new Cat();

        TestPro testPro = new TestPro();

        TestPro.Dog a2 = testPro.new Dog();

        System.out.println(testPro.str);

        a1.cry();

        a2.cry();

    }

}

  • 接口

在抽象类中,可以包含一个或多个抽象方法;但在接口(interface)中,所有的方法必须都是抽象的,不能有方法体,它比抽象类更加“抽象”。(Java 8的default方法,可以在接口内部包含一些默认的方法实现)

接口中声明的成员变量默认都是 public static final 的,必须显示的初始化。因而在常量声明时可以省略这些修饰符。

1) 接口中只能定义抽象方法,这些方法默认为 public abstract 的,因而在声明方法时可以省略这些修饰符。试图在接口中定义实例变量、非抽象的实例方法及静态方法,都是非法的。例如:

public interface SataHdd{

    //连接线的数量

    public int connectLine; //编译出错,connectLine被看做静态常量,必须显式初始化

    //写数据

    protected void writeData(String data); //编译出错,必须是public类型

    //读数据

    public static String readData(){ //编译出错,接口中不能包含静态方法

        return "数据"; //编译出错,接口中只能包含抽象方法,

    }

}

  • 接口中没有构造方法,不能被实例化。
    3) 一个接口不实现另一个接口,但可以继承多个其他接口。接口的多继承特点弥补了类的单继承。

实现接口的格式如下:
修饰符 class 类名 extends 父类 implements 多个接口 {
实现方法
}

  • 抽象类与接口
  • 都不能被实例化。
  • 抽象类可以为部分方法提供实现,避免了在子类中重复实现这些方法,提高了代码的可重用性,这是抽象类的优势;而接口中只能包含抽象方法,不能包含任何实现。
  • 一个类只能继承一个直接的父类(可能是抽象类),但一个类可以实现多个接口,这个就是接口的优势。

抽象类方式中,抽象类可以拥有任意范围的成员数据,同时也可以拥有自己的非抽象方法,但是接口方式中,它仅能够有静态、不能修改的成员数据(但是我们一般是不会在接口中使用成员数据),同时它所有的方法都必须是抽象的。在某种程度上来说,接口是抽象类的特殊化。

对子类而言,它只能继承一个抽象类(这是java为了数据安全而考虑的),但是却可以实现多个接口。

abstract class Door{

    abstract void open();

    abstract void close();

}

interface Alarm{

    void alarm();

}

class AlarmDoor extends Door implements Alarm{

    void open(){}

    void close(){}

    void alarm(){}

}

综上所述,接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:

  • 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
  • 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
  • 泛型

 

// 定义泛型类

class Point<T1, T2>{ //注意类型参数位置

    T1 x;

    T2 y;

    public T1 getX() {

        return x;

    }

    public void setX(T1 x) {

        this.x = x;

    }

    public T2 getY() {

        return y;

    }

    public void setY(T2 y) {

        this.y = y;

    }

    // 定义泛型方法

    public <T1, T2> void printPoint(T1 x, T2 y){ //类型参数需要放在修饰符后面、返回值类型前面

        T1 m = x;

        T2 n = y;

        System.out.println("This point is:" + m + ", " + n);

    }

}

T1, T2 是自定义的标识符,也是参数,用来传递数据的类型,而不是数据的值,我们称之为类型参数。习惯上使用单个大写字母,通常情况下,K 表示键,V 表示值,E 表示异常或错误,T 表示一般意义上的数据类型。

 

public <T extends Number> T getMax(T array[]){

    T max = null;

    for(T element : array){

        max = element.doubleValue() > max.doubleValue() ? element : max;

    }

    return max;

}

  • Java 泛型使用 <? super T> <? extends T>
    <? extends T> T类的某一种子类, 表示包括T在内的任何T的子类
    <? super T> T类的某一种超类, 表示包括T在内的任何T的父类
    请记住PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super。
  • <T extends Number> 表示 T 只接受 Number 及其子类,传入其他类型的数据会报错。这里的限定使用关键字 extends,后面可以是类也可以是接口。如果是类,只能有一个;但是接口可以有多个,并以“&”分隔,例如 <T extends Interface1 & Interface2>。

这里的 extends 关键字已不再是继承的含义了,应该理解为 T 是继承自 Number 类的类型,或者 T 是实现了 XX 接口的类型。

  • 异常处理

Java异常处理通过5个关键字控制:try、catch、throw、throws和 finally。

try {
    // block of code to monitor for errors
}
catch (ExceptionType1 exOb) {
    // exception handler for ExceptionType1
}
finally {
    // block of code to be executed before try block ends
}

Finally块中的代码在任何方法返回之前都一定会被执行。

class Test {

    static void exc(){

        try{

            throw new NullPointerException();

        }

        catch(NullPointerException e){

            System.out.println("空指针:"+e);

           throw e;

        }

    }

    public static void main(String[] args) {

        try{

           exc();

       }catch(NullPointerException ex){

            System.out.println("再次捕获异常:"+ex);

        }

    }

}

throw是语句抛出一个异常。
语法:throw (异常对象);
throw e;

throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)
语法:[(修饰符)](返回值类型)(方法名)([参数列表])[throws(异常类)]{……}
public void doA(int a) throws Exception1,Exception3{......}

如:

void doA(int a) throws Exception1,Exception3{
    try{
        ......

    }catch(Exception1 e){
        throw e;
    }catch(Exception2 e){
       System.out.println("出错了!");
    }
    if(a!=b)
        throw new  Exception3("自定义异常");
}

为exception2已经被处理了(System.out.println),所以该方法可能会抛出exception1和exception3异常。

throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。
throws语句用在方法声明后面,表示再抛出异常,由该方法的调用者来处理。

throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。
throw是具体向外抛异常的动作,所以它是抛出一个异常实例。

  • Finally块

finally创建一个代码块。该代码块在一个try/catch 块完成之后另一个try/catch出现之前执行。finally块无论有没有异常抛出都会执行。

try {
    System.out.println("inside procA");
    throw new RuntimeException("demo");
} finally {
    System.out.println("procA's finally");
}

  • java 异常处理 Throwable Error 和Exception

Checked exception需要开发者自己去进行异常处理,不然编译无法通过。而unchecked exception开发者可以不进行异常处理程序也可以正常编译,但程序运行到异常的地方会自动抛出异常(上面的RuntimeException都是unchecked的)。

  • unchecked exception(非检查异常)

也称运行时异常(RuntimeException),比如常见的NullPointerException、IndexOutOfBoundsException。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。

  • checked exception(检查异常,编译异常)

也称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。

  • Error(错误)

是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。

  • Exception(异常)

是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

  • 区别
  1. Checked异常必须被显式地捕获或者传递,而unchecked异常则可以不必捕获或抛出。
  2. Checked异常继承lang.Exception类。Unchecked异常继承自java.lang.RuntimeException类。

除了Error与RuntimeException,其他剩下的异常都是你需要关心的,而这些异常类统称为Checked Exception,至于Error与RuntimeException则被统称为Unchecked Exception。

当程序执行过程中,遇到uncheck exception,则程序中止,不再执行之后的代码。

表 1-1 Java 的 java.lang 中定义的未检查异常子类
异常 说明
ArithmeticException 算术错误,如被0除
ArrayIndexOutOfBoundsException 数组下标出界
ArrayStoreException 数组元素赋值类型不兼容
ClassCastException 非法强制转换类型
IllegalArgumentException 调用方法的参数非法
IllegalMonitorStateException 非法监控操作,如等待一个未锁定线程
IllegalStateException 环境或应用状态不正确
IllegalThreadStateException 请求操作与当前线程状态不兼容
IndexOutOfBoundsException 某些类型索引越界
NullPointerException 非法使用空引用
NumberFormatException 字符串到数字格式非法转换
SecurityException 试图违反安全性
StringIndexOutOfBounds 试图在字符串边界之外索引
UnsupportedOperationException 遇到不支持的操作

 

表 1-2  java.lang 中定义的检查异常
异常 意义
ClassNotFoundException 找不到类
CloneNotSupportedException 试图克隆一个不能实现Cloneable接口的对象
IllegalAccessException 对一个类的访问被拒绝
InstantiationException 试图创建一个抽象类或者抽象接口的对象
InterruptedException 一个线程被另一个线程中断
NoSuchFieldException 请求的字段不存在
NoSuchMethodException 请求的方法不存在

 

断言用于证明和测试程序的假设,比如“这里的值大于 5”。
断言可以在运行时从代码中完全删除,所以对代码的运行速度没有影响。

断言有两种方法:

  • 一种是 assert<<布尔表达式>> ;
  • 另一种是 assert<<布尔表达式>> :<<细节描述>>;

如果布尔表达式的值为false , 将抛出AssertionError 异常,并在异常中输出细节描述。

  • Java多线程

Java在进程间同步性的老模式基础上实行了另一种方法:管程(monitor)。一旦线程进入管程,所有线程必须等待直到该线程退出了管程。

public static void main(String[] args) {

    Thread t = Thread.currentThread();

    System.out.println(t);

    t.setName("wocao");

    System.out.println(t);

    try{

        for(int i=5;i>0;i--){

            System.out.println(i);

            Thread.sleep(1000);

        }

    }catch(InterruptedException e){

        System.out.println("Exception get");

    }

    System.out.println("finished");

}

输出结果:

Thread[main,5,main]
Thread[wocao,5,main]
5
4
3
2
1

该显示顺序:线程名称,优先级以及组的名称。默认情况下,主线程的名称是main。它的优先级是5,这也是默认值,main也是所属线程组的名称。一个线程组(thread group)是一种将线程作为一个整体集合的状态控制的数据结构。

  • Thread和Runnable
  • 在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口。继承Thread类的要重写run()方法,然后用它的实例执行start()方法。实现Runnable接口的要实现run()方法,然后用Thread的public Thread(Runnabletarget) 方法开启多线程。

但是一个类只能继承一个父类,这是继承Thread方法的局限。

  • Runnable接口和Thread之间的联系:

public class Thread extends Object implements Runnable

判定线程是否结束方法:第一种isAlive()。这种方法由Thread定义,如果所调用线程仍在运行,isAlive()方法返回true,如果不是则返回false。但isAlive()很少用到,等待线程结束的更常用的方法是调用join(),描述如下:
final void join( ) throws InterruptedException。

实现Runnable接口的一个例子:

import java.io.*;

import java.lang.*;

class Test implements Runnable

{

    public void run(){

        for(int i=0;i<5;i++){

            System.out.println(Thread.currentThread().getName()+":"+i);

            try{

            Thread.sleep(1000);

            }catch(InterruptedException e){

            System.out.println("Exception");

            }

        }

    }

    public static void main (String[] args) throws java.lang.Exception

    {

        Thread t1 = new Thread(new Test(),"线程1");

        Thread t2 = new Thread(new Test(),"线程2");

        t1.start();

        t2.start();

        try{

            t1.join();

            t2.join();

        } catch (InterruptedException ex) {

            System.out.println("thread Exception caught");

        }

        System.out.println("finished");

    }

}

打印结果:

线程1:0

线程2:0

线程1:1

线程2:1

线程1:2

线程2:2

线程1:3

线程2:3

线程1:4

线程2:4

finished

  • 线程同步

Synchronzied关键字的作用一个词概括就是:线程同步。它可以用来修改对象中的方法,将对象加锁。相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A,没有的话,直接运行。

Synchronzied关键字包括两种用法:synchronized 方法和 synchronized 块。

  1. synchronized 方法

如:public synchronized void accessVal(int newVal);

synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。

  1. synchronized 块

synchronized 块可以对方法的某一部分加锁,用起来更加方便。

如:

synchronized(syncObject) {

    //允许访问控制的代码

}

synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。

synchronzied(this)(获得的是一个该实例的锁(this是当前对象))。当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

public synchronized void Push(char c){同步代码;}
相当于:
public void Push(char c){
    synchronized(this){同步代码;}
}

  • 线程间通信

为避免轮询,Java包含了通过wait( ),notify( )和notifyAll( )方法实现的一个进程间通信机制。这些方法在对象中是用final方法实现的,所以所有的类都含有它们。这三个方法仅在synchronized方法中才能被调用。尽管这些方法从计算机科学远景方向上来说具有概念的高度先进性,实际中用起来是很简单的:

  • wait( ) 告知被调用的线程放弃管程进入睡眠直到其他线程进入相同管程并且调用notify( )。
  • notify( ) 恢复相同对象中第一个调用 wait( ) 的线程。
  • notifyAll( ) 恢复相同对象中所有调用 wait( ) 的线程。具有最高优先级的线程最先运行。

这些方法在Object中被声明,如下所示:
final void wait( ) throws InterruptedException
final void notify( )
final void notifyAll( )
wait( )存在的另外的形式允许你定义等待时间。

 

public class Goods {

    private int seq = 0;

    private boolean hasOne = false;

    public synchronized int getSeq() {

        if (!hasOne) {

            try {

                this.wait();

            } catch (InterruptedException ex) {

                System.out.println("InterruptedException caught");

            }

        }

        System.out.println("得到了序号:" + seq);

        hasOne = false;

        notify();

        return seq;

    }

 

    public synchronized void setSeq(int seq) {

        if (hasOne) {

            try {

                wait();

            } catch (InterruptedException ex) {

                System.out.println("InterruptedException caught");

            }

        }

        System.out.println("设置了序号:" + seq);

        this.seq = seq;

        hasOne = true;

        notify();

    }

 

    public static void main(String[] args) {

        Goods goods = new Goods();

        Producer producer = new Producer(goods);

        Consumer consumer = new Consumer(goods);

    }

}

 

class Producer implements Runnable {

 

    Goods goods;

    public Producer(Goods goods) {

        this.goods = goods;

        new Thread(this, "producer").start();

    }

    public void run() {

        for (int i = 0; i < 5; i++) {

            goods.setSeq(i);

        }

    }

}

 

class Consumer implements Runnable {

    Goods goods;

    public Consumer(Goods goods) {

        this.goods = goods;

        new Thread(this, "consumer").start();

    }

 

    public void run() {

        for (int i = 0; i < 5; i++) {

            goods.getSeq();

        }

    }

}

运行结果:

设置了序号:0

得到了序号:0

设置了序号:1

得到了序号:1

设置了序号:2

得到了序号:2

设置了序号:3

得到了序号:3

设置了序号:4

得到了序号:4

  • 线程死锁

假定一个线程进入了对象X的管程而另一个线程进入了对象Y的管程。如果X的线程试图调用Y的同步方法,它将像预料的一样被锁定。而此时如果Y的线程再试图调用X的同步方法,则会造成死锁(互相等待对方释放锁)。

Class A{

    synchronized void caller(B b){

        b.last();

    }

    synchronized void last(B b){

    }

}

Class B{

    synchronized void caller(A a){

        a.last();

    }

    synchronized void last(A a){

    }

}

如上,当两个线程分别进入到A.m1()和B.m1()执行时,两个线程都请求对方的同步方法,此时会发生死锁。

  • 线程挂起、恢复和终止

先于Java2的版本,程序用Thread 定义的suspend() 和 resume() 来暂停和再启动线程。它们的形式如下:
final void suspend( ) //挂起线程
final void resume( ) //线程恢复,用此方法可以恢复上面挂起的线程

Thread定义的suspend(),resume()和stop()方法可能会造成严重的系统故障。假定对关键的数据结构的一个线程被锁定的情况,如果该线程在那里挂起,这些锁定的线程并没有放弃对资源的控制。其他的等待这些资源的线程可能死锁,所以在Java2被舍弃了。所以使用wait()和notify()方法控制线程的执行。

输入输出(IO)操作

  • 输入输出流

在Java中,把不同类型的输入输出源抽象为流,其中输入和输出的数据称为数据流(Data Stream)。

为了提高数据的传输效率,引入了缓冲流(Buffered Stream)的概念,即为一个流配备一个缓冲区(Buffer),一个缓冲区就是专门用于传送数据的一块内存。

I/O流类概述

为了方便流的处理,Java语言提供了java.io包,在该包中的每一个类都代表了一种特定的输入或输出流。为了使用这些流类,编程时需要引入这个包。 Java提供了两种类型的输入输出流:一种是面向字节的流,数据的处理以字节为基本单位;另一种是面向字符的流,用于字符数据的处理。字节流(Byte Stream)每次读写8位二进制数,也称为二进制字节流或位流。字符流一次读写16位二进制数,并将其做一个字符而不是二进制位来处理。需要注意的是,为满足字符的国际化表示,Java语言的字符编码采用的是16位的Unicode码,而普通文本文件中采用的是8位ASCⅡ码。

java.io中类的层次结构如图所示。
针对一些频繁的设备交互,Java语言系统预定了3个可以直接使用的流对象,分别是:

  • in(标准输入),通常代表键盘输入。
  • out(标准输出):通常写往显示器。
  • err(标准错误输出):通常写往显示器。

在Java语言中使用字节流和字符流的步骤基本相同,以输入流为例,首先创建一个与数据源相关的流对象,然后利用流对象的方法从流输入数据,最后执行close()方法关闭流。

java.IO层次体系结构:

在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable.掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识了

Java I/O主要包括如下几个层次,包含三个部分:

1.流式部分――IO的主体部分;

2.非流式部分――主要包含一些辅助流式部分的类,如:File类、RandomAccessFile类和FileDescriptor等类;

3.其他类–文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。

主要的类如下:

  1. File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
  2. InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
  3. OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。

     Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。

  1. Reader(文件格式操作):抽象类,基于字符的输入操作。
  2. Writer(文件格式操作):抽象类,基于字符的输出操作。
  3. RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作

Java中IO流的体系结构如图:

 

 

IO框架:

 

    

IO流的具体分类

一、按I/O类型来总体分类:

  1.    Memory1)从/向内存数组读写数据: CharArrayReader、 CharArrayWriter、ByteArrayInputStream、ByteArrayOutputStream
    2)从/向内存字符串读写数据 StringReader、StringWriter、StringBufferInputStream
     2.Pipe管道  实现管道的输入和输出(进程间通信): PipedReader、PipedWriter、PipedInputStream、PipedOutputStream
    3.File 文件流。对文件进行读、写操作 :FileReader、FileWriter、FileInputStream、FileOutputStream
    4. ObjectSerialization 对象输入、输出 :ObjectInputStream、ObjectOutputStream
    5.DataConversion数据流 按基本数据类型读、写(处理的数据是Java的基本类型(如布尔型,字节,整数和浮点数)):DataInputStream、DataOutputStream
     6.Printing 包含方便的打印方法 :PrintWriter、PrintStream
    7.Buffering缓冲  在读入或写出时,对数据进行缓存,以减少I/O的次数:BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream
    8.Filtering 滤流,在数据进行读或写时进行过滤:FilterReader、FilterWriter、FilterInputStream、FilterOutputStream过
    9.Concatenation合并输入 把多个输入流连接成一个输入流 :SequenceInputStream
    10.Counting计数  在读入数据时对行记数 :LineNumberReader、LineNumberInputStream
    11.Peeking Ahead 通过缓存机制,进行预读 :PushbackReader、PushbackInputStream
    12.Converting between Bytes and Characters 按照一定的编码/解码标准将字节流转换为字符流,或进行反向转换(Stream到Reader,Writer的转换类):InputStreamReader、OutputStreamWriter

二、按数据来源(去向)分类:
1、File(文件): FileInputStream, FileOutputStream, FileReader, FileWriter
2、byte[]:ByteArrayInputStream, ByteArrayOutputStream
3、Char[]: CharArrayReader, CharArrayWriter
4、String: StringBufferInputStream, StringReader, StringWriter
5、网络数据流:InputStream, OutputStream, Reader, Writer

如何选择IO流:

1)确定是数据源和数据目的(输入还是输出)

源:输入流 InputStream Reader
目的:输出流 OutputStream Writer

2)明确操作的数据对象是否是纯文本

是:字符流Reader,Writer
否:字节流InputStream,OutputStream

3)明确具体的设备。

是硬盘文件:File++:

读取:FileInputStream,, FileReader,

写入:FileOutputStream,FileWriter
是内存用数组

byte[]:ByteArrayInputStream, ByteArrayOutputStream
是char[]:CharArrayReader, CharArrayWriter
是String:StringBufferInputStream(已过时,因为其只能用于String的每个字符都是8位的字符串), StringReader, StringWriter
是网络用Socket流

是键盘:用System.in(是一个InputStream对象)读取,用System.out(是一个OutoutStream对象)打印

3)是否需要转换流

是,就使用转换流,从Stream转化为Reader,Writer:InputStreamReader,OutputStreamWriter

4)是否需要缓冲提高效率

是就加上Buffered:BufferedInputStream, BufferedOuputStream, BuffereaReader, BufferedWriter
5)是否需要格式化输出

 

例:将一个文本文件中数据存储到另一个文件中。
1)数据源和数据目的:读取流,InputStream Reader  输出:OutputStream Writer
2)是否纯文本:是!这时就可以选择Reader Writer。
3)设备:是硬盘文件。Reader体系中可以操作文件的对象是 FileReader FileWriter。

FileReader fr = new FileReader(“a.txt”);

FileWriter fw = new FileWriter(“b.txt”);
4)是否需要提高效率:是,加Buffer
BufferedReader bfr = new BufferedReader(new FileReader(“a.txt”);  );
BufferedWriter bfw = new BufferedWriter(new FileWriter(“b.txt”);  );

 

面向字符的输入流:

Reader和Writer是java.io包中所有字符流的父类。由于它们都是抽象类,所以应使用它们的子类来创建实体对象,利用对象来处理相关的读写操作。Reader和Writer的子类又可以分为两大类:一类用来从数据源读入数据或往目的地写出数据(称为节点流),另一类对数据执行某种处理(称为处理流)。

面向字符的输入流类都是Reader的子类,其类层次结构如图所示。
Reader的类层次结构图
Reader 的主要子类及说明:

Reader 的主要子类
类名 功能描述
CharArrayReader 从字符数组读取的输入流
BufferedReader 缓冲输入字符流
PipedReader 输入管道
InputStreamReader 将字节转换到字符的输入流
FilterReader 过滤输入流
StringReader 从字符串读取的输入流
LineNumberReader 为输入数据附加行号
PushbackReader 返回一个字符并把此字节放回输入流
FileReader 从文件读取的输入流

Reader 所提供的方法如下表所示,可以利用这些方法来获得流内的位数据。

Reader 的常用方法
方法 功能描述
void close() 关闭输入流
void mark() 标记输入流的当前位置
boolean markSupported() 测试输入流是否支持 mark
int read() 从输入流中读取一个字符
int read(char[] ch) 从输入流中读取字符数组
int read(char[] ch, int off, int len) 从输入流中读 len 长的字符到 ch 内
boolean ready() 测试流是否可以读取
void reset() 重定位输入流
long skip(long n) 跳过流内的 n 个字符

 

  • 常用类库、向量与哈希

Math类

Math类提供了常用的数学运算方法以及Math.PI和Math.E两个数学常量。该类是final的,不能被继承,类中的方法和属性全部是静态,不允许在类的外部创建Math类的对象。因此,只能使用Math类的方法而不能对其作任何更改。下表列出了Math类的主要方法。

Math类的主要方法
方法 功能
int abs(int i) 求整数的绝对值(另有针对long、float、double的方法)
double ceil(double d) 不小于d的最小整数(返回值为double型)
double floor(double d) 不大于d的最大整数(返回值为double型)
int max(int i1,int i2) 求两个整数中最大数(另有针对long、float、double的方法)
int min(int i1,int i2) 求两个整数中最小数(另有针对long、float、double的方法)
double random() 产生0~1之间的随机数
int round(float f) 求最靠近f的整数
long round(double d) 求最靠近d的长整数
double sqrt(double a) 求平方根
double sin(double d) 求d的sin值(另有求其他三角函数的方法如cos,tan,atan)
double log(double x) 求自然对数
double exp(double x) 求e的x次幂(ex
double pow(double a, double b) 求a的b次幂

 

字符串类

字符串是字符的序列。在 Java 中,字符串无论是常量还是变量都是用类的对象来实现的。java.lang 提供了两种字符串类:String 类和 StringBuffer 类。

1.String 类
按照 Java 语言的规定,String 类是 immutable 的 Unicode 字符序列,其作用是实现一种不能改变的静态字符串。实际上,所有改变字符串的结果都是生成新的字符串,而不是改变原来字符串。
2.StringBuffer 类
String 类不能改变字符串对象中的内容,只能通过建立一个新串来实现字符串的变化。如果字符串需要动态改变,就需要用 StringBuffer 类。StringBuffer 类主要用来实现字符串内容的添加、修改、删除,也就是说该类对象实体的内存空间可以自动改变大小,以便于存放一个可变的字符序列。

StringBuffer 类提供的三种构造方法
构造方法 说明
StringBuffer() 使用该无参数的构造方法创建的 StringBuffer 对象,初始容量为 16 个字符,当对象存放的字符序列大于 16 个字符时,对象的容量自动增加。该对象可以通过 length()方法获取实体中存放的字符序列的长度,通过 capacity()方法获取当前对象的实际容量。
StringBuffer(int length) 使用该构造方法创建的 StringBuffer 对象,其初始容量为参数 length 指定的字符个数,当对象存放的字符序列的长度大于 length 时,对象的容量自动增加,以便存放所增加的字符。
StringBuffer(Strin str) 使用该构造方法创建的 StringBuffer 对象,其初始容量为参数字符串 str 的长度再加上 16 个字符。

 

几种 StringBuffer 类常用的方法
方法 说明
append() 使用 append() 方法可以将其他 Java 类型数据转化为字符串后再追加到 StringBuffer 的对象中。
insert(int index, String str) insert() 方法将一个字符串插入对象的字符序列中的某个位置。
setCharAt(int n, char ch) 将当前 StringBuffer 对象中的字符序列 n 处的字符用参数 ch 指定的字符替换,n 的值必须是非负的,并且小于当前对象中字符串序列的长度。
reverse() 使用 reverse()方法可以将对象中的字符序列翻转。
delete(int n, int m) 从当前 StringBuffer 对象中的字符序列删除一个子字符序列。这里的 n 指定了需要删除的第一个字符的下标,m 指定了需要删除的最后一个字符的下一个字符的下标,因此删除的子字符串从 n~m-1。
replace(int n, int m, String str) 用 str 替换对象中的字符序列,被替换的子字符序列由下标 n 和 m 指定。

 

 

  • 哈希表

数组和向量都可以存储对象,但对象的存储位置是随机的,也就是说对象本身与其存储位置之间没有必然的联系。当要查找一个对象时,只能以某种顺序(如顺序查找或二分查找)与各个元素进行比较,当数组或向量中的元素数量很多时,查找的效率会明显的降低。

一种有效的存储方式,是不与其他元素进行比较,一次存取便能得到所需要的记录。这就需要在对象的存储位置和对象的关键属性(设为 k)之间建立一个特定的对应关系(设为 f),使每个对象与一个唯一的存储位置相对应。在查找时,只要根据待查对象的关键属性 k 计算f(k)的值即可。如果此对象在集合中,则必定在存储位置 f(k)上,因此不需要与集合中的其他元素进行比较。称这种对应关系 f 为哈希(hash)方法,按照这种思想建立的表为哈希表。

Java 使用哈希表类(Hashtable)来实现哈希表,以下是与哈希表相关的一些概念:

  • 容量(Capacity):Hashtable 的容量不是固定的,随对象的加入其容量也可以自动增长。
  • 关键字(Key):每个存储的对象都需要有一个关键字,key 可以是对象本身,也可以是对象的一部分(如某个属性)。要求在一个 Hashtable 中的所有关键字都是唯一的。
  • 哈希码(Hash Code):若要将对象存储到 Hashtable 上,就需要将其关键字 key 映射到一个整型数据,成为 key 的哈希码。
  • 项(Item):Hashtable 中的每一项都有两个域,分别是关键字域 key 和值域 value(存储的对象)。Key 和 value 都可以是任意的 Object 类型的对象,但不能为空。
  • 装填因子(Load Factor):装填因子表示为哈希表的装满程度,其值等于元素数比上哈希表的长度。

public class Test {

    public static void main(String[] args) throws IOException {

        Hashtable h = new Hashtable();

        h.put("1", 1);

        h.put(2, 2);

        Set set = h.entrySet();

        for(Iterator i = set.iterator();i.hasNext();){

            System.out.println(i.next());

        }

        System.out.println("--------------------------------1");

        Set s = h.keySet();

        for(Iterator i = s.iterator();i.hasNext();){

            System.out.println(i.next());

        }

 

        System.out.println("--------------------------------2");

        Collection c = h.values();

        Iterator iterator = c.iterator();

        while(iterator.hasNext()){

            System.out.println(iterator.next());

        }

        System.out.println("--------------------------------3");

        for(Enumeration e = h.keys();e.hasMoreElements();){

            System.out.println(e.nextElement());

        }

        System.out.println("--------------------------------4");

        for(Enumeration e = h.elements();e.hasMoreElements();){

            System.out.println(e.nextElement());

        }

    }

}

输出结果:

1=1

2=2

——————————–1

1

2

——————————–2

1

2

——————————–3

1

2

——————————–4

1

2

  • 几个重要的java数据库访问类和接口

编写访问数据库的Java程序还需要几个重要的类和接口。

DriverManager类

DriverManager类处理驱动程序的加载和建立新数据库连接。DriverManager是java.sql包中用于管理数据库驱动程序的类。通常,应用程序只使用类DriverManager的getConnection()静态方法,用来建立与数据库的连接,返回Connection对象:
static Connection getConnection(String url,String username,String password)
指定数据的URL用户名和密码创建数据库连接对象。url的语法格式是:
jdbc:<数据库的连接机制>:<ODBC数据库名>。

Connection类

Connection类是java.sql包中用于处理与特定数据库连接的类。Connection对象是用来表示数据库连接的对象,Java程序对数据库的操作都在这种对象上进行。Connection类的主要方法有:

  1. Statement createStatement():创建一个Statement对象。
  2. Statement createStatement(int resultSetType,int resultSetConcurrency):创建一个Statement对象,生成具有特定类型的结果集。
  3. void commit():提交对数据库的改动并释放当前持有的数据库的锁。
  4. void rollback():回滚当前事务中的所有改动并释放当前连接持有的数据库的锁。
  5. String getCatalog():获得连接对象的当前目录。
  6. boolean isClose():判断连接是否已关闭。
  7. boolean isReadOnly():判断连接是否为只读模式。
  8. void setReadOnly():设置连接为只读模式。
  9. void close():释放连接对象的数据库和JDBC资源。

Statement类

Statement类是java.sql包中用于在指定的连接中处理SQL语句的类。数据库编程的要点是在程序中嵌入SQL命令。程序需要声明和创建连接数据库的Connection对象,并让该对象连接数据库。调用类DriverManager的静态方法getConnection()获得Connection对象,实现程序与数据库的连。然后,用Statement类声明SQL语句对象,并调用Connection对象的createStatement()方法,创建SQL语句对象。例如,以下代码创建语句对象sql:
Statement sql = null;
try{
    sql = con.createStatement();
}catch(SQLException e){}

ResultSet类

有了SQL语句对象后,调用语句对象的方法executeQuery()执行SQL查询,并将查询结果存放在一个用ResultSet类声明的对象中,例如,以下代码读取学生成绩表存于rs 对象中:
ResultSet rs = sql.executeQuery(“SELECT * FROM ksInfo”);
ResultSet对象实际上是一个由查询结果数据的表,是一个管式数据集,由统一形式的数据行组成,一行对应一条查询记录。在ResultSet对象中隐含着一个游标,一次只能获得游标当前所指的数据行,用next方法可取下一个数据行。用数据行的字段(列)名称或位置索引(自1开始)调用形如getXXX()方法获得记录的字段植 。以下是ResultSet对象的部分方法:

  1. byte getByte(int columnIndex):返回指定字段的字节值。
  2. Date getDate(int columnIndex):返回指定字段的日期值。
  3. float getFloat(int columnIndex):返回指定字段的浮点值。
  4. int getInt(int columnIndex):返回指定字段的整数值。
  5. String getString(int columnIndex):返回指定字段的字符串值。
  6. double getDouble(String columnName):返回指定字段的双精度值。
  7. long getLong(String columnName):返回指定字段的long型整值。
  8. boolean next():返回是否还有下一字段。

以上方法中的columnIndex是位置索引,用于指定字段,columnName是字段名。

用户需要在查询结果集上浏览,或前后移动、或显示结果集的指定记录,这称为可滚动结果集。程序要获得一个可滚动结果集,只要在获得SQL的语句对象时,增加指定结果集的两个参数即可。例如,以下代码:
Statement stmt = con.createStatement(type,concurrency);
ResultSet rs = stmt.executeQuery(SQL语句)
语句对象stmt的SQL查询就能得到相应类型的结果集。

  • int 型参数type决定可滚动集的滚动方式:
    • TYPE_FORWORD_ONLY,结果集的游标只能向下滚动。
    • TYPE_SCROLL_INSENSITIVE,游标可上下移动,当数据库变化时,当前结果集不变。
    • TYPE_SCROLL_SENSITIVE,游标可上下移动,当数据库变化时,当前结果集同步改变。
  • int 型参数concurrency决定数据库是否与可滚动集同步更新:
    • CONCUR_READ_ONLY,不能用结果集更新数据库中的表。
    • CONCUR_UPDATETABLE,能用结果集更新数据库中的表。

例如,以下代码利用连接对象connect,创建Statement对象stmt,指定结果集可滚动,并以只读方式读数据库:
stmt = connect.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
可滚动集上另外一些常用的方法如下:

  1. boolean previous():将游标向上移动,当移到结果集的第一行时,返回false。
  2. void beforeFirst():将游标移结果集的第一行之前。
  3. void afterLast():将游标移到结果集的最后一行之后。
  4. void first():将游标移到第一行。
  5. void last():将游标移到最后一行。
  6. boolean isAfterLast():判游标是否在最后一行之后。
  7. boolean isBeforeFirst():判游标是否在第一行之前。
  8. boolean isLast():判游标是否在最后一行。
  9. boolean isFirst():判游标是否在第一行。
  10. int getRow():获取当前所指的行(行号自1开始编号,结果集空,返回0)。
  11. boolean absolute(int row):将游标移到row行。

 

  • 数据库插入、更新、删除记录

插入数据表记录有3种方案

一.使用Statement对象

实现插入数据表记录的SQL语句的语法是:
insert into 表名(字段名1,字段名2,……)value (字段值1,字段值2,……)
例如:
insert into ksInfo(考号,姓名,成绩,地址,简历)value(‘200701’,’张大卫’,534,’上海欧阳路218弄4-1202’,’xxx’)
实现同样功能的Java程序代码是:
sql = “insert intoksIno(考号,姓名,成绩,地址,简历)”;
sql= = sq1+ “value(‘”+txtNo.getTxt()+’,’”+txtName.getText(0”’,”;
sql = sql+txtScore.getText();
sql=sql+”,’”+txtAddr.getText()+”’,’”+txtResume.getText()+”’)”;
stmt.executeUpdate(sql);

二.使用ResultSet对象

使用ResultSet对象的方法moveToInsertRow()将数据表游标移到插入位置,输入数据后,用方法insertRow()插入记录。例如,以下示意代码:
String sql= “select * from ksInfo”;//生成SQL语句
ResultSet rs = stmt.executeQuery(sql);//获取数据表结果集
rs.moveToInsertRow();//将数据表游标移到插入记录位置
rs.updateString(1,’200701’);//向考号字段填入数据
rs.updateString(2,’张大卫’);//向名字字段填入数据
rs.updateInt(3,534);//向成绩字段填入数据
rs.updateString(4,’上海欧阳路218弄4-1202’);//向地址字段填入数据
rs.updateString(5,’’);//向简历字段填入数据
try{rs.insertRow();}catch(Exception e){};//完成插入

三.使用PrepareStatement对象

与使用Statement对象的方法类似,只是创建SQL语句时暂时用参数?表示值,然后由SQL语句对象生成PrepareStatement对象,插入时通过设定实际参数,实现记录的更新。示意代码如下:
sql = “insert into ksInfo(考号,姓名,成绩,地址,简历)value (?,?,?,?,’’)”;
PrepareStatement pStmt = connect.prepareStatement(sql);
pStmt.setString(1,’200701’);//向考号字段填入数据
pStmt. setString (2,’张大卫’);//向名字字段填入数据
pStmt.setInt(3,534);//向成绩字段填入数据
pStmt. setString (4,’上海欧阳路218弄4-1202’);//向地址字段填入数据
pStmt. setString (5,’’);//向简历字段填入数据
pStmt.executeUpdate();

修改数据表记录也有3种方案。

一.使用Statement对象

实现修改数据表记录的SQL语句的语法是:
update表名 set 字段名1 = 字段值1,字段名2 = 字段值2,……where特定条件
例如:
update ksInfo set 姓名 = ‘张小卫’where 姓名 = ‘张大卫’
先创建一个SQL语句,然砶调用Statement对象的executeUpdate()方法。例如,
sql = “update ksInfo set 姓名 = ‘”+txtName.getText();
sql = sql + “,成绩=”+txtScore.getText();
sql = sql +”,地址=’”+txtAddr.getText();
sql= sql+”’,,简历=’”+txtResume.getText()+”’where 考号=”+txtNo.getText();
stmt.executeUpdate(sql);

二.使用ResultSet对象

先建立ResultSet对象,然后直接设定记录的字段值,修改数据表的记录。例如,
String sql = “select * from ksInfo where 姓名=’张大卫’”;//生成SQL语句
ResultSet rs = stmt.executeQuery(sql);//获取数据表结果集
if(rs.next()){
rs.updateString(2,’张小卫’);
try{rs.updateRow();}catch(Exception e){}
}

三.使用PrepareStatement对象

创建SQL语句时,暂时用参数?表示值,然后由SQL语句对象生成PrepareStatement对象,接着通过设定实际参数实现记录的更新。示意代码:
sql = “update ksInfo set 姓名=? where 姓名 = ‘张大卫’;
PrepareStatement pStmt = connect.prepareStatement(sql);
pStmt.setString(2,’张小卫’);//向名字字段填入数据
pStmt.executeUpdate();

删除数据表也有3种方案

一.使用Statement对象

删除数据表记录的SQL语句的语法是:
delete from 表名 where 特定条件
例如 :
delete from ksInfo where 姓名 = ‘张大卫’
先创建一个SQL语句,然后调用Statement对象的executeUpdate()方法:
stmt.executeUpdate(sql);

二.使用ResultSet对象

先创建一个SQL语句,然后调用Statement对象的executeUpdate()方法。例如:
String sql = “select * from ksInfo where 姓名 = ‘张大卫’”;//生成SQL语句
ResultSet rs = stmt.executeQuery(sql);//获取数据表结果集
if(rs.next()){
rs.deleteRow();try{ rs.updateRow();}catch(Exception e){}
}

三.使用PrepareStatement对象

创建SQL语句时,暂时用参数?表示值,然后由SQL语句对象生成PrepareStatement对象,接着设定实际参数实现特定记录的删除。例如,以下示意代码:
sql = “delete form ksInfo where 姓名=?”;
PrepareStatement pStmt = connect.prepareStatement(sql);
pStmt.setString(2,’张大卫’);//给名字字段指定数据
pStmt.executeUpdate();