1、test.html 测试页
<html>
<head>
<title>测试页面</title>
<style>
.list {
border-top:1 solid #8A2BE2;
border-left:1 solid #8A2BE2;
border-right:1 solid #8A2BE2;
}
.list td {
border-bottom: 1 solid #8A2BE2;
}
</style>
<script>
function $(el) {
return document.getElementById(el);
}
function showWin(param) {
window.showModalDialog("dailog.htm", param, "dialogWidth:" +param.width +"px;dialogHeight:"+param.height+"px;center:yes;help:no;scroll:no;status:no;resizable:no");
}
function TB(tbid) {
this.tb = typeof(tbid) == "string"? $(tbid): tbid;
this.getValue = function(rowIndex, cellIndex){
var trs = this.tb.rows[rowIndex];
var _td = trs.cells[cellIndex];
return _td.innerText;
}
this.setValue = function(rowIndex, cellIndex, value) {
var _tr = this.tb.rows[rowIndex];
var _td = _tr.cells[cellIndex];
_td.innerText = value;
}
/********获取行索引********/
this.findRowIndex = function(eventSrc) {
var _tr = eventSrc; //eventSrc事件源,必须在TD里获事件源是TD或TR本身
while(_tr.tagName != "TR") {
_tr = _tr.parentNode;
}
var trs = this.tb.rows;
for(var i = 0; i < trs.length; i++){
if(_tr == trs[i]) return i;
}
}
}
function edit() {
var tb = new TB("data");
rIndex = tb.findRowIndex(event.srcElement);
$("updateRowIndex").value = rIndex;
$("userName").value = tb.getValue(rIndex, 1); //获得姓名
$("sex").value = tb.getValue(rIndex, 2); //获得性别
$("age").value = tb.getValue(rIndex, 3); //获得年龄
showWin({title:"修改用户信息", width:390, height:230, _div:"openWin",parent:window});
}
function saveAndUpdateView(){
var updateRowIndex = $("updateRowIndex").value;
var tb = new TB($f("data")); //$f()在dailog.html定义,获到的table是父窗口中的table
tb.setValue(updateRowIndex, 1, $("userName").value);
tb.setValue(updateRowIndex, 2, $("sex").value);
tb.setValue(updateRowIndex, 3, $("age").value);
close();
}
</script>
</head>
<body>
<p style="margin-top:60px">
<center>
<table id="data" class="list" width="460px">
<tr>
<td>编号</td>
<td>用户名</td>
<td>性别</td>
<td>年龄</td>
<td>操作</td>
</tr>
<tr>
<td>1</td>
<td>李永胜</td>
<td>男</td>
<td>27</td>
<td><span style="background:#FAEBD7;cursor:hand" onclick="edit();"> 修改 </span></td>
</tr>
<tr>
<td>2</td>
<td>林兄</td>
<td>男</td>
<td>27</td>
<td><span style="background:#FAEBD7;cursor:hand" onclick="edit();"> 修改 </span></td>
</tr>
<tr>
<td>3</td>
<td>叶兄</td>
<td>男</td>
<td>23</td>
<td><span style="background:#FAEBD7;cursor:hand" onclick="edit();"> 修改 </span></td>
</tr>
</table>
</center>
</p>
<!---弹出窗口显示的内容---->
<div id="openWin" style="display:none;">
<form>
<fieldSet>
<legend>修改用户</legend>
<table>
<tr>
<td>用户名</td><td><input type="text" id="userName"/></td>
</tr>
<tr>
<td>性别</td><td><input type="text" id="sex"/></td>
</tr>
<tr>
<td>年龄</td><td><input type="text" id="age"/></td>
</tr>
</table>
</fieldSet>
<input type="hidden" id="updateRowIndex"/>
</form>
<span style="background:#FAEBD7;cursor:hand" onclick="saveAndUpdateView();"> 修改 </span>
</div>
</body>
</html>
2、dailog.html 窗口原型
<html>
<head>
<script>
var param = window.dialogArguments; //传过来的模式对话框窗口参数
document.title = param.title; //窗口标题,必须在窗口创建前实现s
/********将父窗口的js加载进来********/
var scripts = param.parent.document.scripts;
var _head = document.getElementsByTagName("head")[0];
for(var n = 0; n < scripts.length; n++) {
if(scripts[n].src) {
var _script = newEl("script");
_script.src = scripts[n].src;
bind(_head, _script);
}else{//加载直接在html文档中写的script
var _script = newEl("script");
_script.text = scripts[n].text;
bind(_head, _script);
}
}
/*******根据ID获得父窗口的元素*********/
function $f(el) {
return param.parent.document.getElementById(el);
}
/***********创建一个HTML元素*******/
function newEl(tagName) {
return document.createElement(tagName);
}
/***********追加元素***************/
function bind(ower, child) {
ower.appendChild(child);
}
/*******在浏览器完成对象的装载后立即触发*********/
window.onload = function() {
var winDiv;
if(typeof(param._div) == "string") {
winDiv = param.parent.document.getElementById(param._div); //父窗口window对象,因为param._div对象在父窗口
}else{//直接传对象过来
winDiv = param._div;
}
$("mainDiv").innerHTML = winDiv.innerHTML; //将DIV内容在弹出窗口中渲染
}
</script>
</head>
<body>
<center>
<div id="mainDiv" style="margin-top:20px;width:90%"></div>
</center>
</body>
</html>
posted @
2008-05-05 10:43 虎啸龙吟 阅读(1905) |
评论 (0) |
编辑 收藏
转自http://www.blogjava.net/flyffa/archive/2006/12/14/87722.html
基本方法:
基本的方法,网上到处都是,在 java 中就是在 web.xml 注册一个 Listener ,如下:
<listener>
<listener-class>xp.web.SessionCounter</listener-class>
</listener>
SessionCounter.java 实现 javax.servlet.http.HttpSessionListener 接口,分别在 sessionCreated 方法和 sessionDestroyed 方法中处理 session 数目。
这样的方法有一定的问题:
1 、对于真正从网页访问的和搜索引擎的 spider 无法区分。
2 、当 Tomcat 重启时,加载了上次持久化的 session 时,无法准确计算在线数。
第二个问题我们可以不予考虑,这是 tomcat 容器实现不标准的问题,我们要解决的是的第一个问题,如何知道你的访问的是真实的。
用 js 绕过搜索引擎 :
做过 pv 统计的都知道,可以用 script 的方式得到你真实的 pageView 数目,我们现在要做的就是这样的一件事情,我们在所有的页面都加入一段话:
<script type="text/javascript">
document.write ("<iframe src='/sessionCountServlet' width=0 height=0 frameborder=no border=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=no></iframe>");
</script>
然后我们写上一个 servlet 来记录这些真正的访问者。
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class SessionCounterServlet extends HttpServlet {
public SessionCounterServlet() {
super();
}
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException,
ServletException {
process(request, response);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws IOException,
ServletException {
process(request, response);
}
public void process(HttpServletRequest request,
HttpServletResponse response) throws IOException,
ServletException {
SessionCounter.put(request.getSession().getId());
}
}
我们可以看到这个 servlet 只是做了一件事情,在 process 里面做了 SessionCounter.put(request.getSession().getId()); 这个动作。
我们来看看我们的 SessionCounter 做了些什么:
import javax.servlet.http.*;
import java.util.Hashtable;
public class SessionCounter implements HttpSessionListener {
public SessionCounter() {
}
public static Hashtable m_real = new Hashtable();
private static long count = 0;
public void sessionCreated(HttpSessionEvent e) {
count++;
}
public void sessionDestroyed(HttpSessionEvent e) {
if (count > 0) {
count--;
}
m_real.remove(e.getSession().getId());
}
public static long getSessionCount() {
return count;
}
public static void put(String sessionId){
m_real.put(sessionId,"1");
}
public static int getRealCount(){
return m_real.size();
}
}
我们记录了一个静态的 hash 表来记录激活状态的 sessionid ,并在 session 销毁的时候将这个 sessionid 置为空。
怎么把 servlet 配置到 web 应用中我就不罗唆了。
posted @
2008-04-21 11:37 虎啸龙吟 阅读(296) |
评论 (0) |
编辑 收藏
这部分的内容基本与Hibernate一致.JPA同样支持3种类型的继承形式:
1.Single Table Strategy ,单表策略,一张表包含基类与子类的所有数据,很多情况下都是采用这样的冗余设计,通过一个discriminator来区分
2.Table Per Class Strategy ,每个子类对应一张表,每张表都拥有基类的属性
3.Join Strategy ,仍然是每个子类对应一张表,但此表中不包含基类的属性,仅仅是此子类的扩展属性,共享基类的属性
以一个例子来说明3种情况:
一.单表策略
比如Pet作为基类,Cat和Dog继承此类并拥有自己的扩展属性,如:
package com.denny_blue.ejb3.inheritance;
import java.io.Serializable;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "animal_type", discriminatorType = DiscriminatorType.STRING)
public class Pet implements Serializable {
private int id;
private String name;
private double weight;
public Pet() {
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
}
Pet类值的注意的就是通过@Inheritance(strategy = InheritanceType.SINGLE_TABLE)确定采用单表策略,通过@DiscriminatorColumn确定了标志值的字段和类型,我想熟悉hibernate的朋友对这些都应该很熟悉.然后是两个子类:
//Cat.java
package com.denny_blue.ejb3.inheritance;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue("cat")
public class Cat extends Pet {
private String HairBall;
public String getHairBall() {
return HairBall;
}
public void setHairBall(String hairBall) {
HairBall = hairBall;
}
}
//Dog.java
package com.denny_blue.ejb3.inheritance;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(discriminatorType=DiscriminatorType.STRING)
@DiscriminatorValue("dog")
public class Dog extends Pet {
private String trick;
public String getTrick() {
return trick;
}
public void setTrick(String trick) {
this.trick = trick;
}
}
两个子类最值的关注的就是@DiscriminatorValue注释,比如Cat的此值为cat,意味着当Cat类型的Entity存入数据库时,JPA将自动把cat的值赋给animal_type字段,Dog的值则为dog,由此就可以在同一张表中区分开两个不同的子类.
二.Table per Class
采用Table Per Class策略的话,每个子类都将单独建表,并且都独立拥有基类中的所有属性,互相之间不共享,在我们的例子中所要进行的修改很小,像这样:
//基类
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Pet implements Serializable {
private int id;
private String name;
private double weight;
........
//子类:不需要任何设置
@Entity
public class Dog extends Pet {
private String trick;
.......
.......
三.Join策略
每个子类同样独立建表,基类也独立建表,只不过所有的子类的表中只有扩展属性,他们共享基类的表,在我们的例子中修改下即可:
//基类
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Pet implements Serializable {
private int id;
private String name;
private double weight;
........
//子类
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Dog extends Pet {
private String trick;
.......
.......
这部分的内容实在没什么新意,与hibernate完全一致.JAVA EE5向spring和hibernate借鉴了太多东西.
{}
posted @
2008-04-21 11:20 虎啸龙吟 阅读(310) |
评论 (0) |
编辑 收藏
Oracle sql 性能优化调整
1. 选用适合的ORACLE优化器
ORACLE的优化器共有3种:
a. RULE (基于规则)
b. COST (基于成本)
c. CHOOSE (选择性)
设置缺省的优化器,可以通过对init.ora文件中OPTIMIZER_MODE参数的各种声明,如RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS . 你当然也在SQL句级或是会话(session)级对其进行覆盖。
为了使用基于成本的优化器(CBO, Cost-Based Optimizer) , 你必须经常运行analyze 命令,以增加数据库中的对象统计信息(object statistics)的准确性。
如果数据库的优化器模式设置为选择性(CHOOSE),那么实际的优化器模式将和是否运行过analyze命令有关。 如果table已经被analyze过, 优化器模式将自动成为CBO , 反之,数据库将采用RULE形式的优化器。
在缺省情况下,ORACLE采用CHOOSE优化器,为了避免那些不必要的全表扫描(full table scan) , 你必须尽量避免使用CHOOSE优化器,而直接采用基于规则或者基于成本的优化器。
2. 访问Table的方式ORACLE 采用两种访问表中记录的方式
a. 全表扫描
全表扫描就是顺序地访问表中每条记录。 ORACLE采用一次读入多个数据块(database block)的方式优化全表扫描。
b. 通过ROWID访问表
你可以采用基于ROWID的访问方式情况,提高访问表的效率, ROWID包含了表中记录的物理位置信息……ORACLE采用索引(INDEX)实现了数据和存放数据的物理位置(ROWID)之间的联系。 通常索引提供了快速访问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高。
3. 共享SQL语句
为了不重复解析相同的SQL语句,在第一次解析之后, ORACLE将SQL语句存放在内存中。这块位于系统全局区域SGA(system global area)的共享池(shared buffer pool)中的内存可以被所有的数据库用户共享。 因此,当你执行一个SQL语句(有时被称为一个游标)时,如果它和之前的执行过的语句完全相同, ORACLE就能很快获得已经被解析的语句以及最好的执行路径。 ORACLE的这个功能大大地提高了SQL的执行性能并节省了内存的使用。
可惜的是ORACLE只对简单的表提供高速缓冲(cache buffering) ,这个功能并不适用于多表连接查询。数据库管理员必须在init.ora中为这个区域设置合适的参数,当这个内存区域越大,就可以保留更多的语句,当然被共享的可能性也就越大了。当你向ORACLE 提交一个SQL语句,ORACLE会首先在这块内存中查找相同的语句。
这里需要注明的是,ORACLE对两者采取的是一种严格匹配,要达成共享,SQL语句必须完全相同(包括空格,换行等)。
共享的语句必须满足三个条件:
A. 字符级的比较:
当前被执行的语句和共享池中的语句必须完全相同。
例如:
SELECT * FROM EMP;
和下列每一个都不同
SELECT * from EMP;
Select * From Emp;
SELECT * FROM EMP;
B. 两个语句所指的对象必须完全相同:
例如:
用户 对象名 如何访问
Jack sal_limit private synonym
Work_city public synonym
Plant_detail public synonym
Jill sal_limit private synonym
Work_city public synonym
Plant_detail table owner
考虑一下下列SQL语句能否在这两个用户之间共享。
C. 两个SQL语句中必须使用相同的名字的绑定变量(bind variables)
例如:第一组的两个SQL语句是相同的(可以共享),而第二组中的两个语句是不同的(即使在运行时,赋于不同的绑定变量相同的值)
a.
selectpin,namefrompeoplewherepin=:blk1.pin;
selectpin,namefrompeoplewherepin=:blk1.pin;
b.
selectpin,namefrompeoplewherepin=:blk1.ot_ind;
selectpin,namefrompeoplewherepin=:blk1.ov_ind;
4. 选择最有效率的表名顺序(只在基于规则的优化器中有效)
ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表 driving table)将被最先处理。 在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。当ORACLE处理多个表时, 会运用排序及合并的方式连接它们。首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行派序,然后扫描第二个表(FROM子句中最后第二个表),最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。
例如:
表 TAB1 16,384 条记录
表 TAB2 1 条记录
选择TAB2作为基础表 (最好的方法)
select count(*) from tab1,tab2 执行时间0.96秒
选择TAB2作为基础表 (不佳的方法)
select count(*) from tab2,tab1 执行时间26.09秒
如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表。
例如: EMP表描述了LOCATION表和CATEGORY表的交集。
SELECT *
FROM LOCATION L ,
CATEGORY C,
EMP E
WHERE E.EMP_NO BETWEEN 1000 AND 2000
AND E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
将比下列SQL更有效率
SELECT *
FROM EMP E ,
LOCATION L ,
CATEGORY C
WHERE E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
AND E.EMP_NO BETWEEN 1000 AND 2000
1. WHERE子句中的连接顺序。
ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾。
例如:
(低效,执行时间156.3秒)
SELECT…
FROMEMPE
WHERESAL>50000
ANDJOB=‘MANAGER’
AND25<(SELECTCOUNT(*)FROMEMP
WHEREMGR=E.EMPNO);
(高效,执行时间10.6秒)
SELECT…
FROMEMPE
WHERE25<(SELECTCOUNT(*)FROMEMP
WHEREMGR=E.EMPNO)
ANDSAL>50000
ANDJOB=‘MANAGER’;
2. SELECT子句中避免使用 ‘ * ’
当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用 ‘*’ 是一个方便的方法。不幸的是,这是一个非常低效的方法。 实际上,ORACLE在解析的过程中, 会将‘*’ 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。
3. 减少访问数据库的次数
当执行每条SQL语句时, ORACLE在内部执行了许多工作: 解析SQL语句, 估算索引的利用率, 绑定变量 , 读数据块等等。 由此可见, 减少访问数据库的次数 , 就能实际上减少ORACLE的工作量。
例如,以下有三种方法可以检索出雇员号等于0342或0291的职员。
方法1 (最低效)
SELECTEMP_NAME,SALARY,GRADE
FROMEMP
WHEREEMP_NO=342;
SELECTEMP_NAME,SALARY,GRADE
FROMEMP
WHEREEMP_NO=291;
方法2 (次低效)
DECLARE
CURSORC1(E_NONUMBER)IS
SELECTEMP_NAME,SALARY,GRADE
FROMEMP
WHEREEMP_NO=E_NO;
BEGIN
OPENC1(342);
FETCHC1INTO…,..,..;
OPENC1(291);
FETCHC1INTO…,..,..;
CLOSEC1;
END;
方法3 (高效)
以下是引用片段:
SELECTA.EMP_NAME,A.SALARY,A.GRADE,
B.EMP_NAME,B.SALARY,B.GRADE
FROMEMPA,EMPB
WHEREA.EMP_NO=342
ANDB.EMP_NO=291;
注意:
在SQL*Plus , SQL*Forms和Pro*C中重新设置ARRAYSIZE参数, 可以增加每次数据库访问的检索数据量 ,建议值为200.
4. 使用DECODE函数来减少处理时间
使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表。
例如:
SELECTCOUNT(*),SUM(SAL)
FROM EMP
WHEREDEPT_NO=0020
ANDENAMELIKE ‘SMITH%’;
SELECTCOUNT(*),SUM(SAL)
FROM EMP
WHEREDEPT_NO=0030
ANDENAMELIKE ‘SMITH%’;
你可以用DECODE函数高效地得到相同结果
SELECTCOUNT(DECODE(DEPT_NO,0020,’X’,NULL))D0020_COUNT,
COUNT(DECODE(DEPT_NO,0030,’X’,NULL))D0030_COUNT,
SUM(DECODE(DEPT_NO,0020,SAL,NULL))D0020_SAL,
SUM(DECODE(DEPT_NO,0030,SAL,NULL))D0030_SAL
FROMEMPWHEREENAMELIKE‘SMITH%’;
类似的,DECODE函数也可以运用于GROUP BY 和ORDER BY子句中。
5. 整合简单,无关联的数据库访问
如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系)
例如:
SELECTNAME
FROMEMP
WHEREEMP_NO=1234;
SELECTNAME
FROMDPT
WHEREDPT_NO=10;
SELECTNAME
FROMCAT
WHERECAT_TYPE=‘RD’;
上面的3个查询可以被合并成一个:
SELECTE.NAME,D.NAME,C.NAME
FROMCATC,DPTD,EMPE,DUALX
WHERENVL(‘X’,X.DUMMY)=NVL(‘X’,E.ROWID(+))
ANDNVL(‘X’,X.DUMMY)=NVL(‘X’,D.ROWID(+))
ANDNVL(‘X’,X.DUMMY)=NVL(‘X’,C.ROWID(+))
ANDE.EMP_NO(+)=1234
ANDD.DEPT_NO(+)=10
ANDC.CAT_TYPE(+)=‘RD’;
1. 删除重复记录
最高效的删除重复记录方法 ( 因为使用了ROWID)
DELETE FROM EMP E
WHERE E.ROWID > (SELECT MIN(X.ROWID)
FROM EMP X
WHERE X.EMP_NO = E.EMP_NO);
2. 用TRUNCATE替代DELETE
当删除表中的记录时,在通常情况下, 回滚段(rollback segments ) 用来存放可以被恢复的信息。 如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况)
而当运用TRUNCATE时, 回滚段不再存放任何可被恢复的信息。当命令运行后,数据不能被恢复。因此很少的资源被调用,执行时间也会很短。
(译者按: TRUNCATE只在删除全表适用,TRUNCATE是DDL不是DML)
3. 尽量多使用COMMIT
只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少:COMMIT所释放的资源:
a. 回滚段上用于恢复数据的信息。
b. 被程序语句获得的锁
c. redo log buffer 中的空间
d. ORACLE为管理上述3种资源中的内部花费
(译者按: 在使用COMMIT时必须要注意到事务的完整性,现实中效率和事务完整性往往是鱼和熊掌不可得兼)
4. 计算记录条数
和一般的观点相反, count(*) 比count(1)稍快 , 当然如果可以通过索引检索,对索引列的计数仍旧是最快的。 例如 COUNT(EMPNO)
(译者按: 在CSDN论坛中,曾经对此有过相当热烈的讨论, 作者的观点并不十分准确,通过实际的测试,上述三种方法并没有显著的性能差别)
5. 用Where子句替换HAVING子句
避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤。 这个处理需要排序,总计等操作。 如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销。
例如:
低效
SELECTREGION,AVG(LOG_SIZE)
FROMLOCATION
GROUPBYREGION
HAVINGREGIONREGION!=‘SYDNEY’
ANDREGION!=‘PERTH’
高效
SELECTREGION,AVG(LOG_SIZE)
FROMLOCATION
WHEREREGIONREGION!=‘SYDNEY’
ANDREGION!=‘PERTH’
GROUPBYREGION
(译者按: HAVING 中的条件一般用于对一些集合函数的比较,如COUNT() 等等。 除此而外,一般的条件应该写在WHERE子句中)
6. 减少对表的查询
在含有子查询的SQL语句中,要特别注意减少对表的查询。
例如:
低效
SELECTTAB_NAME
FROMTABLES
WHERETAB_NAME=(SELECTTAB_NAME
FROMTAB_COLUMNS
WHEREVERSION=604)
AND DB_VER=(SELECTDB_VER
FROMTAB_COLUMNS
WHEREVERSION=604)
高效
SELECTTAB_NAME
FROMTABLES
WHERE(TAB_NAME,DB_VER)
=(SELECTTAB_NAME,DB_VER)
FROMTAB_COLUMNS
WHEREVERSION=604)
Update 多个Column 例子:
低效:
UPDATEEMP
SETEMP_CAT=(SELECTMAX(CATEGORY)FROMEMP_CATEGORIES)
1. 使用表的别名(Alias)
当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上。这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误。
(Column歧义指的是由于SQL中不同的表具有相同的Column名,当SQL语句中出现这个Column时,SQL解析器无法判断这个Column的归属)
2. 用EXISTS替代IN
在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接。在这种情况下, 使用EXISTS(或NOT EXISTS)通常将提高查询的效率。
低效:
SELECT*
FROMEMP(基础表)
WHEREEMPNO>0
ANDDEPTNOIN(SELECTDEPTNO
FROMDEPT
WHERELOC=‘MELB’)
高效:
SELECT*
FROMEMP(基础表)
WHEREEMPNO>0
ANDEXISTS(SELECT‘X’
FROMDEPT
WHEREDEPT.DEPTNO=EMP.DEPTNO
ANDLOC=‘MELB’)
(相对来说,用NOT EXISTS替换NOT IN 将更显著地提高效率,下面将指出)
3. 用NOT EXISTS替代NOT IN
在子查询中,NOT IN子句将执行一个内部的排序和合并。 无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历)。 为了避免使用NOT IN ,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS.
例如:
SELECT…
FROMEMP
WHEREDEPT_NONOTIN(SELECTDEPT_NO
FROMDEPT
WHEREDEPT_CAT=’A’);
为了提高效率。改写为:
(方法一: 高效)
SELECT….
FROMEMPA,DEPTB
WHEREA.DEPT_NO=B.DEPT(+)
ANDB.DEPT_NOISNULL
ANDB.DEPT_CAT(+)=‘A’
1. 用EXPLAIN PLAN 分析SQL语句
EXPLAIN PLAN 是一个很好的分析SQL语句的工具,它甚至可以在不执行SQL的情况下分析语句。 通过分析,我们就可以知道ORACLE是怎么样连接表,使用什么方式扫描表(索引扫描或全表扫描)以及使用到的索引名称。
你需要按照从里到外,从上到下的次序解读分析的结果。 EXPLAIN PLAN分析的结果是用缩进的格式排列的, 最内部的操作将被最先解读, 如果两个操作处于同一层中,带有最小操作号的将被首先执行。
NESTED LOOP是少数不按照上述规则处理的操作, 正确的执行路径是检查对NESTED LOOP提供数据的操作,其中操作号最小的将被最先处理。
通过实践, 感到还是用SQLPLUS中的SET TRACE 功能比较方便。
举例:
SQL> list
1 SELECT *
2 FROM dept, emp
3* WHERE emp.deptno = dept.deptno
SQL> set autotrace traceonly /*traceonly 可以不显示执行结果*/
SQL> /
14 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 NESTED LOOPS
2 1 TABLE ACCESS (FULL) OF 'EMP'
3 1 TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
4 3 INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
Statistics
----------------------------------------------------------
0 recursive calls
2 db block gets
30 consistent gets
0 physical reads
0 redo size
2598 bytes sent via SQL*Net to client
503 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
14 rows processed
通过以上分析,可以得出实际的执行步骤是:
1. TABLE ACCESS (FULL) OF 'EMP'
2. INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
3. TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
4. NESTED LOOPS (JOINING 1 AND 3)
注: 目前许多第三方的工具如TOAD和ORACLE本身提供的工具如OMS的SQL Analyze都提供了极其方便的EXPLAIN PLAN工具。也许喜欢图形化界面的朋友们可以选用它们。
2. 用索引提高效率
索引是表的一个概念部分,用来提高检索数据的效率。 实际上,ORACLE使用了一个复杂的自平衡B-tree结构。 通常,通过索引查询数据比全表扫描要快。 当ORACLE找出执行查询和Update语句的最佳路径时, ORACLE优化器将使用索引。 同样在联结多个表时使用索引也可以提高效率。 另一个使用索引的好处是,它提供了主键(primary key)的唯一性验证。
除了那些LONG或LONG RAW数据类型, 你可以索引几乎所有的列。 通常, 在大型表中使用索引特别有效。 当然,你也会发现, 在扫描小表时,使用索引同样能提高效率。
虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价。 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时, 索引本身也会被修改。 这意味着每条记录的INSERT , DELETE , UPDATE将为此多付出4 , 5 次的磁盘I/O . 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。
定期的重构索引是有必要的。
ALTER INDEX REBUILD
3. 索引的操作
ORACLE对索引有两种访问模式。
索引唯一扫描 ( INDEX UNIQUE SCAN)
大多数情况下, 优化器通过WHERE子句访问INDEX.
表LODGING有两个索引 : 建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER列上的非唯一性索引LODGING$MANAGER.
SELECT*
FROMLODGING
WHERELODGING=‘ROSEHILL’;
在内部 , 上述SQL将被分成两步执行, 首先 , LODGING_PK 索引将通过索引唯一扫描的方式被访问 , 获得相对应的ROWID, 通过ROWID访问表的方式执行下一步检索。
如果被检索返回的列包括在INDEX列中,ORACLE将不执行第二步的处理(通过ROWID访问表)。 因为检索数据保存在索引中, 单单访问索引就可以完全满足查询结果。
下面SQL只需要INDEX UNIQUE SCAN 操作。
SELECTLODGING
FROMLODGING
WHERELODGING=‘ROSEHILL’;
索引范围查询(INDEX RANGE SCAN)
适用于两种情况:
1. 基于一个范围的检索
2. 基于非唯一性索引的检索
例1:
SELECT LODGING FROM LODGING WHERE LODGING LIKE ‘M%’;
WHERE子句条件包括一系列值, ORACLE将通过索引范围查询的方式查询LODGING_PK . 由于索引范围查询将返回一组值, 它的效率就要比索引唯一扫描低一些。
例2:
SELECTLODGING
FROMLODGING
WHEREMANAGER=‘BILLGATES’;
这个SQL的执行分两步, LODGING$MANAGER的索引范围查询(得到所有符合条件记录的ROWID) 和下一步同过ROWID访问表得到LODGING列的值。 由于LODGING$MANAGER是一个非唯一性的索引,数据库不能对它执行索引唯一扫描。
由于SQL返回LODGING列,而它并不存在于LODGING$MANAGER索引中, 所以在索引范围查询后会执行一个通过ROWID访问表的操作。
WHERE子句中, 如果索引列所对应的值的第一个字符由通配符(WILDCARD)开始, 索引将不被采用。在这种情况下,ORACLE将使用全表扫描。
SELECTLODGING
FROMLODGING
WHEREMANAGERLIKE‘%HANMAN’;
1. 基础表的选择
基础表(Driving Table)是指被最先访问的表(通常以全表扫描的方式被访问)。 根据优化器的不同, SQL语句中基础表的选择是不一样的。
如果你使用的是CBO (COST BASED OPTIMIZER),优化器会检查SQL语句中的每个表的物理大小,索引的状态,然后选用花费最低的执行路径。
如果你用RBO (RULE BASED OPTIMIZER) , 并且所有的连接条件都有索引对应, 在这种情况下, 基础表就是FROM 子句中列在最后的那个表。
举例:
SELECTA.NAME,B.MANAGER
FROM WORKERA,
LODGINGB
WHERE A.LODGING=B.LODING;
由于LODGING表的LODING列上有一个索引, 而且WORKER表中没有相比较的索引, WORKER表将被作为查询中的基础表。
2. 多个平等的索引
当SQL语句的执行路径可以使用分布在多个表上的多个索引时, ORACLE会同时使用多个索引并在运行时对它们的记录进行合并, 检索出仅对全部索引有效的记录。
在ORACLE选择执行路径时,唯一性索引的等级高于非唯一性索引。 然而这个规则只有当WHERE子句中索引列和常量比较才有效。如果索引列和其他表的索引类相比较。 这种子句在优化器中的等级是非常低的。
如果不同表中两个想同等级的索引将被引用, FROM子句中表的顺序将决定哪个会被率先使用。 FROM子句中最后的表的索引将有最高的优先级。
如果相同表中两个想同等级的索引将被引用, WHERE子句中最先被引用的索引将有最高的优先级。
举例:
DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引。
SELECTENAME,
FROMEMP
WHEREDEPT_NO=20
ANDEMP_CAT=‘A’;
这里,DEPTNO索引将被最先检索,然后同EMP_CAT索引检索出的记录进行合并。 执行路径如下:
TABLEACCESSBYROWIDONEMP
AND-EQUAL
INDEXRANGESCANONDEPT_IDX
INDEXRANGESCANONCAT_IDX
3. 等式比较和范围比较
当WHERE子句中有索引列, ORACLE不能合并它们,ORACLE将用范围比较。
举例:
DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引。
SELECTENAME
FROMEMP
WHEREDEPTNO>20
ANDEMP_CAT=‘A’;
这里只有EMP_CAT索引被用到,然后所有的记录将逐条与DEPTNO条件进行比较。 执行路径如下:
TABLEACCESSBYROWIDONEMP
INDEXRANGESCANONCAT_IDX
4. 不明确的索引等级
当ORACLE无法判断索引的等级高低差别,优化器将只使用一个索引,它就是在WHERE子句中被列在最前面的。
举例:
DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引。
SELECTENAME
FROMEMP
WHEREDEPTNO>20
ANDEMP_CAT>‘A’;
这里, ORACLE只用到了DEPT_NO索引。 执行路径如下:
TABLEACCESSBYROWIDONEMP
INDEXRANGESCANONDEPT_IDX
译者按:我们来试一下以下这种情况:
SQL> select index_name, uniqueness from user_indexes where table_name = 'EMP';
INDEX_NAME UNIQUENES
------------------------------ ---------
EMPNO UNIQUE
EMPTYPE NONUNIQUE
SQL> select * from emp where empno >= 2 and emp_type = 'A' ;
no rows selected
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
2 1 INDEX (RANGE SCAN) OF 'EMPTYPE' (NON-UNIQUE
虽然EMPNO是唯一性索引,但是由于它所做的是范围比较, 等级要比非唯一性索引的等式比较低!
5. 强制索引失效
如果两个或以上索引具有相同的等级,你可以强制命令ORACLE优化器使用其中的一个(通过它,检索出的记录数量少) .
举例:
SELECTENAME
FROMEMP
WHEREEMPNO=7935
ANDDEPTNO+0=10/*DEPTNO上的索引将失效*/
ANDEMP_TYPE||‘’=‘A’/*EMP_TYPE上的索引将失效*/
这是一种相当直接的提高查询效率的办法。 但是你必须谨慎考虑这种策略,一般来说,只有在你希望单独优化几个SQL时才能采用它。
这里有一个例子关于何时采用这种策略,
假设在EMP表的EMP_TYPE列上有一个非唯一性的索引而EMP_CLASS上没有索引。
SELECTENAME
FROMEMP
WHEREEMP_TYPE=‘A’
ANDEMP_CLASS=‘X’;
优化器会注意到EMP_TYPE上的索引并使用它。 这是目前唯一的选择。 如果,一段时间以后, 另一个非唯一性建立在EMP_CLASS上,优化器必须对两个索引进行选择,在通常情况下,优化器将使用两个索引并在他们的结果集合上执行排序及合并。 然而,如果其中一个索引(EMP_TYPE)接近于唯一性而另一个索引(EMP_CLASS)上有几千个重复的值。 排序及合并就会成为一种不必要的负担。 在这种情况下,你希望使优化器屏蔽掉EMP_CLASS索引。
用下面的方案就可以解决问题。
SELECTENAME
FROMEMP
WHEREEMP_TYPE=‘A’
ANDEMP_CLASS||‘’=‘X’;
1. 避免在索引列上使用计算
WHERE子句中,如果索引列是函数的一部分。优化器将不使用索引而使用全表扫描。
举例:
低效:
SELECT…
FROMDEPT
WHERESAL*12>25000;
高效:
SELECT…
FROMDEPT
WHERESAL>25000/12;
:这是一个非常实用的规则,请务必牢记
2. 自动选择索引
如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性。
在这种情况下,ORACLE将使用唯一性索引而完全忽略非唯一性索引。
举例:
SELECTENAME
FROMEMP
WHEREEMPNO=2326
ANDDEPTNO=20;
这里,只有EMPNO上的索引是唯一性的,所以EMPNO索引将用来检索记录。
TABLEACCESSBYROWIDONEMP
INDEXUNIQUESCANONEMP_NO_IDX
3. 避免在索引列上使用NOT
通常,我们要避免在索引列上使用NOT, NOT会产生在和在索引列上使用函数相同的影响。 当ORACLE“遇到”NOT,他就会停止使用索引转而执行全表扫描。
举例:
低效: (这里,不使用索引)
SELECT…
FROMDEPT
WHEREDEPT_CODENOT=0;
高效: (这里,使用了索引)
SELECT…
FROMDEPT
WHEREDEPT_CODE>0;
需要注意的是,在某些时候, ORACLE优化器会自动将NOT转化成相对应的关系操作符。
NOT > to <=
NOT >= to <
NOT < to >=
NOT <= to >
:在这个例子中,作者犯了一些错误。 例子中的低效率SQL是不能被执行的。
我做了一些测试:
SQL> select * from emp where NOT empno > 1;
no rows selected
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
2 1 INDEX (RANGE SCAN) OF 'EMPNO' (UNIQUE)
SQL> select * from emp where empno <= 1;
no rows selected
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
2 1 INDEX (RANGE SCAN) OF 'EMPNO' (UNIQUE)
两者的效率完全一样,也许这符合作者关于“ 在某些时候, ORACLE优化器会自动将NOT转化成相对应的关系操作符” 的观点。
4. 用>=替代>
如果DEPTNO上有一个索引,
高效:
SELECT*
FROMEMP
WHEREDEPTNO>=4
低效:
SELECT*
FROMEMP
WHEREDEPTNO>3
两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录。
1. 避免在索引列上使用计算
WHERE子句中,如果索引列是函数的一部分。优化器将不使用索引而使用全表扫描。
举例:
低效:
SELECT…
FROMDEPT
WHERESAL*12>25000;
高效:
SELECT…
FROMDEPT
WHERESAL>25000/12;
:这是一个非常实用的规则,请务必牢记
2. 自动选择索引
如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性。
在这种情况下,ORACLE将使用唯一性索引而完全忽略非唯一性索引。
举例:
SELECTENAME
FROMEMP
WHEREEMPNO=2326
ANDDEPTNO=20;
这里,只有EMPNO上的索引是唯一性的,所以EMPNO索引将用来检索记录。
TABLEACCESSBYROWIDONEMP
INDEXUNIQUESCANONEMP_NO_IDX
3. 避免在索引列上使用NOT
通常,我们要避免在索引列上使用NOT, NOT会产生在和在索引列上使用函数相同的影响。 当ORACLE“遇到”NOT,他就会停止使用索引转而执行全表扫描。
举例:
低效: (这里,不使用索引)
SELECT…
FROMDEPT
WHEREDEPT_CODENOT=0;
高效: (这里,使用了索引)
SELECT…
FROMDEPT
WHEREDEPT_CODE>0;
需要注意的是,在某些时候, ORACLE优化器会自动将NOT转化成相对应的关系操作符。
NOT > to <=
NOT >= to <
NOT < to >=
NOT <= to >
:在这个例子中,作者犯了一些错误。 例子中的低效率SQL是不能被执行的。
我做了一些测试:
SQL> select * from emp where NOT empno > 1;
no rows selected
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
2 1 INDEX (RANGE SCAN) OF 'EMPNO' (UNIQUE)
SQL> select * from emp where empno <= 1;
no rows selected
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
2 1 INDEX (RANGE SCAN) OF 'EMPNO' (UNIQUE)
两者的效率完全一样,也许这符合作者关于“ 在某些时候, ORACLE优化器会自动将NOT转化成相对应的关系操作符” 的观点。
4. 用>=替代>
如果DEPTNO上有一个索引,
高效:
SELECT*
FROMEMP
WHEREDEPTNO>=4
低效:
SELECT*
FROMEMP
WHEREDEPTNO>3
两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录。
{http://blog.chinaunix.net/u/20483/showart_546882.html}
posted @
2008-04-20 11:33 虎啸龙吟 阅读(925) |
评论 (0) |
编辑 收藏
http://blog.chinaunix.net/u/21684/showart.php?id=537094
posted @
2008-04-20 11:10 虎啸龙吟 阅读(343) |
评论 (0) |
编辑 收藏
http://www.blogjava.net/zhangchao/archive/2008/03/19/187372.html
posted @
2008-03-20 09:11 虎啸龙吟 阅读(298) |
评论 (0) |
编辑 收藏
http://www.smellcode.cn/index.php/javascript/jiyuext20dehanyoucheckboxdetree/
posted @
2008-03-20 09:08 虎啸龙吟 阅读(413) |
评论 (0) |
编辑 收藏
http://www.blogjava.net/amigoxie/archive/2008/02/20/180779.html
posted @
2008-03-17 20:14 虎啸龙吟 阅读(190) |
评论 (0) |
编辑 收藏
http://www.blogjava.net/pdw2009/archive/2006/05/10/45461.html
posted @
2008-03-09 15:15 虎啸龙吟 阅读(176) |
评论 (0) |
编辑 收藏
HashMap是Java新Collection Framework中用来代替HashTable的一个实现,HashMap和HashTable的区别是: HashMap是未经同步的,而且允许null值。HashTable继承Dictionary,而且使用了Enumeration,所以被建议不要使用。
HashMap的声明如下:
public class HashMap extends AbstractMap implements Map, Cloneable,Serializable
有关AbstractMap:http://blog.csdn.net/treeroot/archive/2004/09/20/110343.aspx
有关Map:http://blog.csdn.net/treeroot/archive/2004/09/20/110331.aspx
有关Cloneable:http://blog.csdn.net/treeroot/archive/2004/09/07/96936.aspx
这个类比较复杂,这里只是重点分析了几个方法,特别是后面涉及到很多内部类都没有解释
不过都比较简单。
static final int DEFAULT_INITIAL_CAPACITY = 16; 默认初始化大小
static final int MAXIMUM_CAPACITY = 1 << 30; 最大初始化大小
static final float DEFAULT_LOAD_FACTOR = 0.75f; 默认加载因子
transient Entry[] table; 一个Entry类型的数组,数组的长度为2的指数。
transient int size; 映射的个数
int threshold; 下一次扩容时的值
final float loadFactor; 加载因子
transient volatile int modCount; 修改次数
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +loadFactor);
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY);
注意:这里应该是一个失误! 应该是:threshold =(int)(DEFAULT_INITIAL_CAPACITY * loadFactor);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
public HashMap(Map m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
putAllForCreate(m);
}
void init() {}
static final Object NULL_KEY = new Object();
static Object maskNull(Object key){
return (key == null ? NULL_KEY : key);
}
static Object unmaskNull(Object key) {
return (key == NULL_KEY ? null : key);
}
static int hash(Object x) {
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
在HashTable中没有这个方法,也就是说HashTable中是直接用对象的hashCode值,但是HashMap做了改进 用这个算法来获得哈希值。
static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
根据哈希值和数组的长度来返回该hash值在数组中的位置,只是简单的与关系。
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public Object get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (true) {
if (e == null) return e;
if (e.hash == hash && eq(k, e.key)) return e.value;
e = e.next;
}
}
这个方法是获取数据的方法,首先获得哈希值,这里把null值掩饰了,并且hash值经过函数hash()修正。 然后计算该哈希值在数组中的索引值。如果该索引处的引用为null,表示HashMap中不存在这个映射。 否则的话遍历整个链表,这里找到了就返回,如果没有找到就遍历到链表末尾,返回null。这里的比较是这样的:e.hash==hash && eq(k,e.key) 也就是说如果hash不同就肯定认为不相等,eq就被短路了,只有在 hash相同的情况下才调用equals方法。现在我们该明白Object中说的如果两个对象equals返回true,他们的 hashCode应该相同的道理了吧。假如两个对象调用equals返回true,但是hashCode不一样,那么在HashMap 里就认为他们不相等。
public boolean containsKey(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (e != null) {
if (e.hash == hash && eq(k, e.key)) return true;
e = e.next;
}
return false;
}
这个方法比上面的简单,先找到哈希位置,再遍历整个链表,如果找到就返回true。
Entry getEntry(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (e != null && !(e.hash == hash && eq(k, e.key)))
e = e.next;
return e;
}
这个方法根据key值返回Entry节点,也是先获得索引位置,再遍历链表,如果没有找到返回的是null。
public Object put(Object key, Object value) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
Object oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, k, value, i);
return null;
}
首先获得hash索引位置,如果该位置的引用为null,那么直接插入一个映射,返回null。如果此处的引用不是null,必须遍历链表,如果找到一个相同的key,那么就更新该value,同时返回原来的value值。如果遍历完了没有找到,说明该key值不存在,还是插入一个映射。如果hash值足够离散的话,也就是说该索引没有被使用的话,那么不不用遍历链表了。相反,如果hash值不离散,极端的说如果是常数的话,所有的映射都会在这一个链表上,效率会极其低下。这里举一个最简单的例子,写两
个不同的类作为key插入到HashMap中,效率会远远不同。
class Good{
int i;
public Good(int i){
this.i=i;
}
public boolean equals(Object o){
return (o instanceof Good) && (this.i==((Good)o).i)
}
public int hashCode(){
return i;
}
}
class Bad{
int i;
public Good(int i){
this.i=i;
}
public boolean equals(Object o){
return (o instanceof Good) && (this.i==((Good)o).i)
}
public int hashCode(){
return 0;
}
}
执行代码:
Map m1=new HashMap();
Map m2=new HashMap();
for(int i=0;i<100;i++){
m1.put(new Good(i),new Integer(i)); //这里效率非常高
}
for(int i=0;i<100;i++){
m2.put(new Bad(i),new Integer(i)); //这里几乎要崩溃
}
上面的是两个非常极端的例子,执行一下就知道差别有多大。
private void putForCreate(Object key, Object value) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
e.value = value;
return;
}
}
createEntry(hash, k, value, i);
}
void putAllForCreate(Map m) {
for (Iterator i = m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry e = (Map.Entry) i.next();
putForCreate(e.getKey(), e.getValue());
}
}
上面的两个方法是被构造函数和clone方法调用的。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (size < threshold || oldCapacity > newCapacity)
return;
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
这个方法在需要的时候重新分配空间,相当于ArrayList的ensureCapacity方法,不过这个更加复杂。
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry e = src[j];
if (e != null) {
src[j] = null;
do {
Entry next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
遍历原来的数组,如果该Entry不是null的话,说明有映射,然后遍历这个链表,把所有的映射插入到新的数组中,注意这里要从新计算索引位置。
public void putAll(Map t) {
int n = t.size();
if (n == 0)
return;
if (n >= threshold) {
n = (int)(n / loadFactor + 1);
if (n > MAXIMUM_CAPACITY)
n = MAXIMUM_CAPACITY;
int capacity = table.length;
while (capacity < n) capacity <<= 1;
resize(capacity);
}
for (Iterator i = t.entrySet().iterator(); i.hasNext(); ) {
Map.Entry e = (Map.Entry) i.next();
put(e.getKey(), e.getValue());
}
}
这个方法先确定是否需要扩大空间,然后循环调用put方法。
public Object remove(Object key) {
Entry e = removeEntryForKey(key);
return (e == null ? e : e.value);
}
Entry removeEntryForKey(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry prev = table[i];
Entry e = prev;
while (e != null) { 如果e==null表示不存在
Entry next = e.next;
if (e.hash == hash && eq(k, e.key)) {
modCount++;
size--;
if (prev == e)
table[i] = next; 链表的第一个元素就是要删除的,这里最好加一句 e.next=null.
else
prev.next = next; 存在担不是链表的第一个元素, 这里最好加一句 e.next=null.
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e; 这里其实就是return null;
}
这个方法其实也不复杂,也是遍历链表,这里建议加一句e.next=null,可以改为
if(prev==e)
table[i]=next;
else
prev.next=next;
e.next=null; 这一句是多加的,可以提高效率。
这里简单说明我的看法:
因为e是被删除的节点,删除它其实就是指向它的指针指向它的后面一个节点。所以e可以作为GC回收的对象。
可以e还有一个next指针指向我们的数据,如果e没有被回收。而且此时e.next指向的节点也变为没用的了,但是
却有一个它的引用(e.next),所以虽然e的下一个节点没用了,但是却不能作为GC回收的对象,除非e先被回收。
虽然不一定会引起很大的问题,但是至少会影响GC的回收效率。就像数据库中的外键引用一样,删除起来很麻烦呀。
Entry removeMapping(Object o) {
if (!(o instanceof Map.Entry))
return null;
Map.Entry entry = (Map.Entry)o;
Object k = maskNull(entry.getKey());
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry prev = table[i];
Entry e = prev;
while (e != null) {
Entry next = e.next;
if (e.hash == hash && e.equals(entry)) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
这个方法和上面的一样。
public void clear() {
modCount++;
Entry tab[] = table;
for (int i = 0; i < tab.length; i++)
tab[i] = null;
size = 0;
}
同样可以改进
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry tab[] = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value)) return true;
return false;
}
private boolean containsNullValue() {
Entry tab[] = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (e.value == null) return true;
return false;
}
public Object clone() {
HashMap result = null;
try {
result = (HashMap)super.clone();
}
catch (CloneNotSupportedException e) { // assert false; }
result.table = new Entry[table.length];
result.entrySet = null;
result.modCount = 0;
result.size = 0;
result.init();
result.putAllForCreate(this);
return result;
}
static class Entry implements Map.Entry {
final Object key;
Object value;
final int hash;
Entry next;
Entry(int h, Object k, Object v, Entry n) {
value = v;
next = n;
key = k;
hash = h;
}
public Object getKey() {
return unmaskNull(key);
}
public Object getValue() {
return value;
}
public Object setValue(Object newValue) {
Object oldValue = value;
value = newValue;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2))) return true;
}
return false;
}
public int hashCode() {
return (key==NULL_KEY ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode());
}
public String toString() {
return getKey() + "=" + getValue();
}
void recordAccess(HashMap m) { }
void recordRemoval(HashMap m) { }
}
一个静态内部类
void addEntry(int hash, Object key, Object value, int bucketIndex) {
table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]);
if (size++ >= threshold)
resize(2 * table.length);
}
注意这个方法,插入连表的头。
可以写成这样更好理解:
Entry oldHead=table[bucketIndex];
Entry newHead = new Entry(hash,key,value,oldHead);
table[bucketIndex]=newHead;
void createEntry(int hash, Object key, Object value, int bucketIndex) {
table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]);
size++;
}
private abstract class HashIterator implements Iterator {
Entry next;
int expectedModCount;
int index;
Entry current;
HashIterator() {
expectedModCount = modCount;
Entry[] t = table;
int i = t.length;
Entry n = null;
if (size != 0) {
while (i > 0 && (n = t[--i]) == null) ;
}
next = n;
index = i;
}
public boolean hasNext() {
return next != null;
}
Entry nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry e = next;
if (e == null)
throw new NoSuchElementException();
Entry n = e.next;
Entry[] t = table;
int i = index;
while (n == null && i > 0)
n = t[--i]; index = i;
next = n;
return current = e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}
private class ValueIterator extends HashIterator {
public Object next() {
return nextEntry().value;
}
}
private class KeyIterator extends HashIterator {
public Object next() {
return nextEntry().getKey();
}
}
private class EntryIterator extends HashIterator {
public Object next() {
return nextEntry();
}
}
Iterator newKeyIterator() {
return new KeyIterator();
}
Iterator newValueIterator() {
return new ValueIterator();
}
Iterator newEntryIterator() {
return new EntryIterator();
}
private transient Set entrySet = null;
public Set keySet() {
Set ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSet {
public Iterator iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
public Collection values() {
Collection vs = values; return (vs != null ? vs : (values = new Values()));
}
private class Values extends AbstractCollection {
public Iterator iterator() {
return newValueIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
HashMap.this.clear();
}
}
public Set entrySet() {
Set es = entrySet;
return (es != null ? es : (entrySet = new EntrySet()));
}
private class EntrySet extends AbstractSet {
public Iterator iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Entry candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
return removeMapping(o) != null;
}
public int size() {
return size;
}
public void clear() {
HashMap.this.clear();
}
}
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeInt(table.length);
s.writeInt(size);
for (Iterator i = entrySet().iterator(); i.hasNext(); ) {
Map.Entry e = (Map.Entry) i.next();
s.writeObject(e.getKey());
s.writeObject(e.getValue());
}
}
private static final long serialVersionUID = 362498820763181265L;
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int numBuckets = s.readInt();
table = new Entry[numBuckets];
init();
size = s.readInt(); for (int i=0;
for (int i=0; i<size; i++) {
Object key = s.readObject();
Object value = s.readObject();
putForCreate(key, value);
}
}
int capacity() {
return table.length;
}
float loadFactor() {
return loadFactor;
}
posted @
2006-07-09 11:38 虎啸龙吟 阅读(493) |
评论 (3) |
编辑 收藏