#
当数据上到好几万多条时,要调用几条数据在页面显现,ASP就会慢如蜗牛.最糟糕的是,当n多用户打开页面拜访的时分,每个用户每次都要读取数据库一次,
这无疑降低了效率,很明显, 假如能把数据能保具有内存上,然后读取,无疑放慢了速度.
所谓缓存其实就是在内存中开辟一个用来保存数据的空间.
使用缓存你就不必屡次的拜访你保具有硬盘上的数据了,由于这些数据我们希望每个用户都能看到成效一样,考虑使用的是application对象,由于它是
一切拜访者的共用的对象,存储的消息和定义的事情能够为一切者拜访者使用,这里要使用asp内置对象application了.
关于application:
2个办法[lock 和 unlock],
2个集合[content 和 staticobjects],
2个事情[开端的application_onstart 和 application_end]
application变量不会由于用户的分开而消失,一旦建立,不断等到网站关闭和程序卸载为止,正由于如此,使用的时分要特别当心!,否则会占用内存
(虚拟主机提供商很不高兴了).
我们是把数据写入一个自定义的application里面,在制定的工夫读取刷新的,大致思绪就是这样.
实例演示.先建立一个简单的数据库,写个function读取一下,写入一个dim变量temp中:
程序代码
<% Function DisplayRecords()
'这个函数本来给一个变量temp付上记载的值
Dim sql, conn, rs
'符合条件的sql语句
sql = "Select id, [szd_f], [szd_t] FROM admin"
'打开数据库连接
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "DRIVER={Microsoft Access Driver (*.mdb)};
DBQ="&Server.MapPath("db.mdb")
Set rs = Server.CreateObject("ADODB.Recordset")
rs.Open sql, conn, 1, 3
'当符合sq语句l的数据没有显现完毕时
If Not rs.EOF Then
'给temp变量赋值
Dim temp
temp = "<table width=""90%"" align=""center"""
temp = temp & " border=""1"" bordercolor=""silver"""
temp = temp & " cellspacing=""2"" cellpadding=""0"">"
temp = temp & "<tr bgcolor=""#CCDDEE""><td width=""5%"""
temp = temp & ">ID</td><td>操作</td>"
temp = temp & "<td>数值</td></tr>"
While Not rs.EOF
temp = temp & "<tr><td bgcolor=""#CCDDEE"">"
temp = temp & rs("ID") & "</td><td>" &
rs("szd_f")
temp = temp & "</td><td>" & rs("szd_t")
temp = temp & "</td></tr>"
rs.MoveNext
Wend
temp = temp & "</table>"
'temp赋值完毕,把它再前往给函数
DisplayRecords = temp
Else
DisplayRecords = "Data Not Available."
End If
'开释内存
rs.Close
conn.Close
Set rs = Nothing
Set conn = Nothing
End Function %>
ok,上面的函数改造完毕,调用的时分就是DisplayRecords.
上面是application大显本领了:
程序代码
<%'该函数是写入缓存
Function DisplayCachedRecords(Secs)
Dim retVal, datVal, temp1
'Secs是每次要刷新数据的工夫, retVal是数据,datVal是剩余工夫
retVal = Application("cache_demo") '获得appliction的值
datVal = Application("cache_demo_date") '获得appliction的值
'判别datVal 的值,也就是要计算工夫过来了没
If datVal = "" Then
'假如是空,datVal值为当前工夫按秒加上secs定义的工夫
datVal = DateAdd("s",Secs,Now)
End If
'temp1是判别当前工夫和datVal的秒差
temp1 = DateDiff("s", Now, datVal)
'假如retVal已经是上面函数的前往值且工夫大于0
If temp1 > 0 And retVal <> "" Then
'本函数前往记载数
DisplayCachedRecords = retVal
Response.Write "<b><font color=""green"">利用缓存读取数据"
Response.Write " ... (" & temp1 & "
秒剩余)</font></b>"
Response.Write "<br><br>"
Else
'retVal 是空的话,就赋予DisplayRecords的值给变量temp2
Dim temp2
temp2 = DisplayRecords()
'保存到Application.------------------>重点
Application.Lock
Application("cache_demo") = temp2
Application("cache_demo_date") = DateAdd("s",Secs,Now)
Application.UnLock
DisplayCachedRecords = temp2
' 这里随便写上了记载的缓存的过来工夫,绝对总秒数倒差 :
Response.Write "<b><font color=""red"">刷新缓存显现 ..."
Response.Write "</font></b><br><br>"
End If
End Function
%>
说明完毕.
调用办法:<%=DisplayCachedRecords(20)%>
一、使用步骤:
1、创建XMLHTTP对象 //需MSXML4.0支持
2、打开与服务端的连接,同时定义指令发送方式,服务网页(URL)和请求权限等。客户端通过Open命令打开与服务端的服务网页的连接。与普
通HTTP指令传送一样,可以用"GET"方法或"POST"方法指向服务端的服务网页。
3、发送指令。
4、等待并接收服务端返回的处理结果。
5、释放XMLHTTP对象
二、XMLHTTP方法:
Open( bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword )
bstrMethod: 数据传送方式,即GET或POST。
bstrUrl: 服务网页的URL。
varAsync:
是否同步执行。缺省为True,即同步执行,但只能在DOM中实施同步执行。用中一般将其置为False,即异步执行。
bstrUser: 用户名,可省略。
bstrPassword:用户口令,可省略。
Send( varBody )
varBody:指令集。可以是XML格式数据,也可以是字符串,流,或者一个无符号整数数组。也可以省略,让指令通过Open方法的URL参数代入。
setRequestHeader( bstrHeader, bstrvalue )
bstrHeader:HTTP 头(header)
bstrvalue: HTTP 头(header)的值
如果Open方法定义为POST,可以定义表单方式上传:
xmlhttp.setRequestHeader( "Content-Type",
"application/x-www-form-urlencoded")
三、XMLHTTP属性:
onreadystatechange:在同步执行方式下获得返回结果的事件句柄。只能在DOM中调用。
responseBody: 结果返回为无符号整数数组。
responseStream: 结果返回为IStream流。
responseText : 结果返回为字符串。
responseXML: 结果返回为XML格式数据。
具体的使用方法
创建XMLHTTP对象的语句如下:
Set objXML = CreateObject("Msxml2.XMLHTTP") 或
Set objXML = CreateObject("Microsoft.XMLHTTP")
'Or for version 3.0 of XMLHTTP, use:
'Set objXML = Server.CreateObject("MSXML2.ServerXMLHTTP")
对象创建后调用Open方法对Request对象进行初始化,语法格式为:
objXML.open http-method,url,async,userID,password
Open方法中包含了5个参数,前三个是必要的,后两个是可选的(在服务器需要进行身份验证时提供)。参数的含义如下所示:
http-method:HTTP的通信方式,比如GET或是POST
url:接收XML数据的服务器的URL地址。通常在URL中要指明ASP或CGI程序
async:一个布尔标识,说明请求是否为异步的。如果是异步通信方式(true),客户机就不等待服务器的响应;如果是同步方式(false),客户机就要等到服务器返回消息后才去执行其他操作
userID:用户ID,用于服务器身份验证
password:用户密码,用于服务器身份验证
XMLHTTP对象的Send方法
用Open方法对Request对象进行初始化后,调用Send方法发送XML数据:
objXML.send()
Send方法的参数类型是Variant,可以是字符串、DOM树或任意数据流。
发送数据的方式分为同步和异步两种。在异步方式下,数据包一旦发送完毕,就结束Send进程,客户机执行其他的操作;而在同步方式下,客户机要等到服务器返回确认消息后才结束Send进程。
XMLHTTP对象中的readyState属性
其能够反映出服务器在处理请求时的进展状况。客户机的程序可以根据这个状态信息设置相应的事件处理方法。属性值及其含义如下表所示:
值 说明
0 Response对象已经创建,但XML文档上载过程尚未结束
1 XML文档已经装载完毕
2 XML文档已经装载完毕,正在处理中
3 部分XML文档已经解析
4 文档已经解析完毕,客户端可以接受返回消息
客户机处理响应信息,客户机接收到返回消息后,进行简单的处理,基本上就完成了C/S之间的一个交互周期。
客户机接收响应是通过XMLHTTP对象的属性实现的:
responseText:将返回消息作为文本字符串;
responseBody:将返回消息作为HTML文档内容;
responseXML:将返回消息视为XML文档,在服务器响应消息中含有XML数据时使用;
responseStream:将返回消息视为Stream对象
以下是一个简单的例子:类似新闻小偷
<%
Set objXML = Server.CreateObject("MSXML2.ServerXMLHTTP")
objXML.open "GET","http://www.alexa.com",false
objXML.send()
response.write(objXML.responseText)
%>
整个步骤很明显:建立、打开、发送和接受。
shift:删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined
var a = [1,2,3,4,5];
var b = a.shift(); //a:[2,3,4,5] b:1
unshift:将参数添加到原数组开头,并返回数组的长度
var a = [1,2,3,4,5];
var b = a.unshift(-2,-1); //a:[-2,-1,1,2,3,4,5] b:7
注:在IE6.0下测试返回值总为undefined,FF2.0下测试返回值为7,所以这个方法的返回值不可靠,需要用返回值时可用splice代替本方法来使用。
pop:删除原数组最后一项,并返回删除元素的值;如果数组为空则返回undefined
var a = [1,2,3,4,5];
var b = a.pop(); //a:[1,2,3,4] b:5
push:将参数添加到原数组末尾,并返回数组的长度
var a = [1,2,3,4,5];
var b = a.push(6,7); //a:[1,2,3,4,5,6,7] b:7
concat:返回一个新数组,是将参数添加到原数组中构成的
var a = [1,2,3,4,5];
var b = a.concat(6,7); //a:[1,2,3,4,5] b:[1,2,3,4,5,6,7]
splice(start,deleteCount,val1,val2,...):从start位置开始删除deleteCount项,并从该位置起插入val1,val2,...
var a = [1,2,3,4,5];
var b = a.splice(2,2,7,8,9); //a:[1,2,7,8,9,5] b:[3,4]
var b = a.splice(0,1); //同shift
a.splice(0,0,-2,-1); var b = a.length; //同unshift
var b = a.splice(a.length-1,1); //同pop
a.splice(a.length,0,6,7); var b = a.length; //同push
reverse:将数组反序
var a = [1,2,3,4,5];
var b = a.reverse(); //a:[5,4,3,2,1] b:[5,4,3,2,1]
sort(orderfunction):按指定的参数对数组进行排序
var a = [1,2,3,4,5];
var b = a.sort(); //a:[1,2,3,4,5] b:[1,2,3,4,5]
slice(start,end):返回从原数组中指定开始下标到结束下标之间的项组成的新数组
var a = [1,2,3,4,5];
var b = a.slice(2,5); //a:[1,2,3,4,5] b:[3,4,5]
join(separator):将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符
var a = [1,2,3,4,5];
var b = a.join("|"); //a:[1,2,3,4,5] b:"1|2|3|4|5"
数组是JavaScript提供的一个内部对象,它是一个标准的集合,我们可以添加(push)、删除(shift)里面元素,我们还可以通过for循环遍历里面的元素,那么除了数组我们在JavaScript里还可以有别的集合吗?
由于JavaScript的语言特性,我们可以向通用对象动态添加和删除属性。所以Object也可以看成是JS的一种特殊的集合。下面比较一下Array和Object的特性:
Array:
新建:var ary = new Array(); 或 var ary = [];
增加:ary.push(value);
删除:delete ary[n];
遍历:for ( var i=0 ; i < ary.length ; ++i ) ary[i];
Object:
新建:var obj = new Object(); 或 var obj = {};
增加:obj[key] = value; (key为string)
删除:delete obj[key];
遍历:for ( var key in obj ) obj[key];
从上面的比较可以看出Object完全可以作为一个集合来使用,在使用Popup窗口创建无限级Web页菜单(3)中我介绍过Eric实现的那个__MenuCache__,它也就是一个模拟的集合对象。
如果我们要在Array中检索出一个指定的值,我们需要遍历整个数组:
1var keyword = ;
2for ( var i=0 ; i < ary.length ; ++i ){
3 if ( ary[i] == keyword ){
4 // todo
5 }
6}
7
而我们在Object中检索一个指定的key的条目,只需要是要使用:
1var key = '';
2var value = obj[key];
3
Object的这个特性可以用来高效的检索Unique的字符串集合,遍历Array的时间复杂度是O(n),而遍历Object的时间复杂度是 O(1)。虽然对于10000次集合的for检索代价也就几十ms,可是如果是1000*1000次检索或更多,使用Object的优势一下就体现出来了。在此之前我做了一个mapping,把100个Unique的字符mapping到1000个字符串数组上,耗时25-30s!后来把for遍历改成了Object模拟的集合的成员引用,同样的数据量mapping,耗时仅1.7-2s!!!
对于集合的遍历效率(从高到低):var value = obj[key]; > for ( ; ; ) > for ( in )。效率最差的就是for( in )了,如果集合过大,尽量不要使用for ( in )遍历。
今天看了一下java的内存分配,分享一下:
基础数据类型直接在栈空间分配, 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收。 引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量 。 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完成后从栈空间回收。局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。 方法调用时传入的 literal 参数,先在栈空间分配,在方法调用完成后从栈空间分配。字符串常量在 DATA 区域分配 ,this 在堆空间分配 。数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!
哦 对了,补充一下static在DATA区域分配。
其实是有规律的,只要你理解了这些个基本的原理:
堆空间的话: 操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。另外由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便,另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
栈空间的话:在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是固定的(是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 由系统自动分配,速度较快。但程序员是无法控制的。
ok,头会不会有点小晕,不会的话继续吧:
JVM中的堆和栈
JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.
从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
PS: 相关内容
方法区
在一个jvm实例的内部,类型信息被存储在一个称为方法区的内存逻辑区中。类型信息是由类加载器在类加载时从类文件中提取出来的。类(静态)变量也存储在方法区中。
jvm实现的设计者决定了类型信息的内部表现形式。如,多字节变量在类文件是以big-endian存储的,但在加载到方法区后,其存放形式由jvm根据不同的平台来具体定义。
jvm在运行应用时要大量使用存储在方法区中的类型信息。在类型信息的表示上,设计者除了要尽可能提高应用的运行效率外,还要考虑空间问题。根据不同的需求,jvm的实现者可以在时间和空间上追求一种平衡。
因为方法区是被所有线程共享的,所以必须考虑数据的线程安全。假如两个线程都在试图找lava的类,在lava类还没有被加载的情况下,只应该有一个线程去加载,而另一个线程等待。
方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。同样方法区也不必是连续的。方法区可以在堆(甚至是虚拟机自己的堆)中分配。jvm可以允许用户和程序指定方法区的初始大小,最小和最大尺寸。
方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展java程序,一些类也会成为垃圾。jvm可以回收一个未被引用类所占的空间,以使方法区的空间最小。
类型信息
对每个加载的类型,jvm必须在方法区中存储以下类型信息:
一 这个类型的完整有效名
二 这个类型直接父类的完整有效名(除非这个类型是interface或是
java.lang.Object,两种情况下都没有父类)
三 这个类型的修饰符(public,abstract, final的某个子集)
四 这个类型直接接口的一个有序列表
类型名称在java类文件和jvm中都以完整有效名出现。在java源代码中,完整有效名由类的所属包名称加一个".",再加上类名
组成。例如,类Object的所属包为java.lang,那它的完整名称为java.lang.Object,但在类文件里,所有的"."都被
斜杠“/”代替,就成为java/lang/Object。完整有效名在方法区中的表示根据不同的实现而不同。
除了以上的基本信息外,jvm还要为每个类型保存以下信息:
类型的常量池( constant pool)
域(Field)信息
方法(Method)信息
除了常量外的所有静态(static)变量
常量池
jvm为每个已加载的类型都维护一个常量池。常量池就是这个类型用到的常量的一个有序集合,包括实际的常量(string,
integer, 和floating point常量)和对类型,域和方法的符号引用。池中的数据项象数组项一样,是通过索引访问的。
因为常量池存储了一个类型所使用到的所有类型,域和方法的符号引用,所以它在java程序的动态链接中起了核心的作用。
域信息
jvm必须在方法区中保存类型的所有域的相关信息以及域的声明顺序,
域的相关信息包括:
域名
域类型
域修饰符(public, private, protected,static,final volatile, transient的某个子集)
方法信息
jvm必须保存所有方法的以下信息,同样域信息一样包括声明顺序
方法名
方法的返回类型(或 void)
方法参数的数量和类型(有序的)
方法的修饰符(public, private, protected, static, final, synchronized, native, abstract的一个子集)除了abstract和native方法外,其他方法还有保存方法的字节码(bytecodes)操作数栈和方法栈帧的局部变量区的大小
异常表
类变量(
Class Variables
译者:就是类的静态变量,它只与类相关,所以称为类变量
)
类变量被类的所有实例共享,即使没有类实例时你也可以访问它。这些变量只与类相关,所以在方法区中,它们成为类数据在逻辑上的一部分。在jvm使用一个类之前,它必须在方法区中为每个non-final类变量分配空间。
常量(被声明为final的类变量)的处理方法则不同,每个常量都会在常量池中有一个拷贝。non-final类变量被存储在声明它的
类信息内,而final类被存储在所有使用它的类信息内。
对类加载器的引用
jvm必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么jvm会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。
jvm在动态链接的时候需要这个信息。当解析一个类型到另一个类型的引用的时候,jvm需要保证这两个类型的类加载器是相同的。这对jvm区分名字空间的方式是至关重要的。
对Class类的引用
jvm为每个加载的类型(译者:包括类和接口)都创建一个java.lang.Class的实例。而jvm必须以某种方式把Class的这个实例和存储在方法区中的类型数据联系起来。
你可以通过Class类的一个静态方法得到这个实例的引用// A method declared in class java.lang.Class:
public static Class forName(String className);
假如你调用forName("java.lang.Object"),你会得到与java.lang.Object对应的类对象。你甚至可以通过这个函数
得到任何包中的任何已加载的类引用,只要这个类能够被加载到当前的名字空间。如果jvm不能把类加载到当前名字空间,
forName就会抛出ClassNotFoundException。
(译者:熟悉COM的朋友一定会想到,在COM中也有一个称为 类对象(Class Object)的东东,这个类对象主要 是实现一种工厂模式,而java由于有了jvm这个中间 层,类对象可以很方便的提供更多的信息。这两种类对象 都是Singleton的)
也可以通过任一对象的getClass()函数得到类对象的引用,getClass被声明在Object类中:
// A method declared in class java.lang.Object:
public final Class getClass();
例如,假如你有一个java.lang.Integer的对象引用,可以激活getClass()得到对应的类引用。
通过类对象的引用,你可以在运行中获得相应类存储在方法区中的类型信息,下面是一些Class类提供的方法:
// Some of the methods declared in class java.lang.Class:
public String getName();
public Class getSuperClass();
public boolean isInterface();
public Class[] getInterfaces();
public ClassLoader getClassLoader();
这些方法仅能返回已加载类的信息。getName()返回类的完整名,getSuperClass()返回父类的类对象,isInterface()判断是否是接口。getInterfaces()返回一组类对象,每个类对象对应一个直接父接口。如果没有,则返回一个长度为零的数组。
getClassLoader()返回类加载器的引用,如果是由启动类加载器加载的则返回null。所有的这些信息都直接从方法区中获得。
方法表
为了提高访问效率,必须仔细的设计存储在方法区中的数据信息结构。除了以上讨论的结构,jvm的实现者还可以添加一些其他的数据结构,如方法表。jvm对每个加载的非虚拟类的类型信息中都添加了一个方法表,方法表是一组对类实例方法的直接引用(包括从父类继承的方法)。jvm可以通过方法表快速激活实例方法。(译者:这里的方法表与C++中的虚拟函数表一样,但java方法全都 是virtual的,自然也不用虚拟二字了。正像java宣称没有 指针了,其实java里全是指针。更安全只是加了更完备的检查机制,但这都是以牺牲效率为代价的,个人认为java的设计者 始终是把安全放在效率之上的,所有java才更适合于网络开发)
一个例子
为了显示jvm如何使用方法区中的信息,我们据一个例子,我们
看下面这个类:
class Lava {
private int speed = 5; // 5 kilometers per hour
void flow() {
}
}
class Volcano {
public static void main(String[] args) {
Lava lava = new Lava();
lava.flow();
}
}
下面我们描述一下main()方法的第一条指令的字节码是如何被执行的。不同的jvm实现的差别很大,这里只是其中之一。
为了运行这个程序,你以某种方式把“Volcano"传给了jvm。有了这个名字,jvm找到了这个类文件(Volcano.class)并读入,它从
类文件提取了类型信息并放在了方法区中,通过解析存在方法区中的字节码,jvm激活了main()方法,在执行时,jvm保持了一个指向当前类(Volcano)常量池的指针。
注意jvm在还没有加载Lava类的时候就已经开始执行了。正像大多数的jvm一样,不会等所有类都加载了以后才开始执行,它只会在需要的时候才加载。
main()的第一条指令告知jvm为列在常量池第一项的类分配足够的内存。jvm使用指向Volcano常量池的指针找到第一项,发现是一个对Lava类的符号引用,然后它就检查方法区看lava是否已经被加载了。
这个符号引用仅仅是类lava的完整有效名”lava“。这里我们看到为了jvm能尽快从一个名称找到一个类,一个良好的数据结构是多么重要。这里jvm的实现者可以采用各种方法,如hash表,查找树等等。同样的算法可以用于Class类的forName()的实现。
当jvm发现还没有加载过一个称为"Lava"的类,它就开始查找并加载类文件"Lava.class"。它从类文件中抽取类型信息并放在了方法区中。
jvm于是以一个直接指向方法区lava类的指针替换了常量池第一项的符号引用。以后就可以用这个指针快速的找到lava类了。而这个替换过程称为常量池解析(constant pool resolution)。在这里我们替换的是一个native指针。
jvm终于开始为新的lava对象分配空间了。这次,jvm仍然需要方法区中的信息。它使用指向lava数据的指针(刚才指向volcano常量池第一项的指针)找到一个lava对象究竟需要多少空间。
jvm总能够从存储在方法区中的类型信息知道某类型对象需要的空间。但一个对象在不同的jvm中可能需要不同的空间,而且它的空间分布也是不同的。(译者:这与在C++中,不同的编译器也有不同的对象模型是一个道理)
一旦jvm知道了一个Lava对象所要的空间,它就在堆上分配这个空间并把这个实例的变量speed初始化为缺省值0。假如lava的父对象也有实例变量,则也会初始化。
当把新生成的lava对象的引用压到栈中,第一条指令也结束了。下面的指令利用这个引用激活java代码把speed变量设为初始值,5。另外一条指令会用这个引用激活Lava对象的flow()方法。
按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
2.2 堆和栈的比较
上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:
在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然 分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时.
堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用 new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).
2.3 JVM中的堆和栈
JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.
从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
JVM运行时,将内存分为堆和栈,堆中存放的是创建的对象,JAVA字符串对象内存实现时,在堆中开辟了一快很小的内存,叫字符串常量池,用来存放特定的字符串对象。
关于String对象的创建,两种方式是不同的,第一种不用new的简单语法,即
String s1="JAVA";
创建步骤是先看常量池中有没有与"JAVA"相同的的字符串对象,如果有,将s1指向该对象,若没有,则创建一个新对象,并让s1指向它。
第二种是new语法
String s2="JAVA";
这种语法是在堆而不是在常量池中创建对象,并将s2指向它,然后去字符串常量池中看看,是否有与之相同的内容的对象,如果有,则将new出来的字符串对象与字符串常量池中的对象联系起来,如果没有,则在字符串常量池中再创建一个包含该内容的字符串对象,并将堆内存中的对象与字符串常量池中新建出来的对象联系起来。
这就是字符串的一次投入,终生回报的内存机制,对字符串的比较带来好处。
<script type="text/javascript">
var str = '[ {"green":10,"size":5,"strock":12},{"green":10,"size":2,"strock":20} ]';
var json = eval(str);
alert(json[1].size);
</script>
通用例遍方法,可用于例遍对象和数组。
不同于例遍 jQuery 对象的 $().each() 方法,此方法可用于例遍任何对象。
回调函数拥有两个参数:第一个为对象的成员或数组的索引,第二个为对应变量或内容。
如果需要退出 each 循环可使回调函数返回 false,其它返回值将被忽略。
返回值:Object
参数:
object(Object) :需要例遍的对象或数组。
callback(Function) :(可选) 每个成员/元素执行的回调函数。
示例:
例遍数组,同时使用元素索引和内容。
jQuery 代码:
1$.each( [0,1,2], function(i, n){
2 alert( "Item #" + i + ": " + n );
3});
例遍对象,同时使用成员名称和变量内容。
jQuery 代码:
1$.each( { name: "John", lang: "JS" }, function(i, n){
2 alert( "Name: " + i + ", Value: " + n );
3});
最近在网上查阅了不少Javascript闭包 (closure)相关的资料,写的大多是非常的学术和专业。对于初学者来说别说理解闭包了,就连文字叙述都很难看懂。撰写此文的目的就是用最通俗的文字揭开Javascript闭包的真实面目。
一、什么是闭包?
“官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。相信很少有人能直接看懂这句话,因为他描述的太学术。我想用如何在Javascript中创建一个闭包来告诉你什么是闭包,因为跳过闭包的创建过程直接理解闭包的定义是非常困难的。看下面这段代码:
1function a(){
2 var i=0;
3 function b(){
4 alert(++i);
5 }
6 return b;
7}
8var c = a();
9c();
这段代码有两个特点:
1、函数b嵌套在函数a内部;
2、函数a返回函数b。
这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数 a外的变量c引用了函数a内的函数b,就是说:当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
我猜想你一定还是不理解闭包,因为你不知道闭包有什么作用,下面让我们继续探索。
二、闭包有什么作用?
简而言之,闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。这是对闭包作用的非常直白的描述,不专业也不严谨,但大概意思就是这样,理解闭包需要循序渐进的过程。
在上面的例子中,由于闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。
那么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。(关于Javascript的垃圾回收机制将在后面详细介绍)
三、闭包内的微观世界
如果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。
1、当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。
2、当函数a执行的时候,a会进入相应的执行环境(excution context)。
3、在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。
4、然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a 的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
5、下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
6、最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。
到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,如下图所示:
如图所示,当在函数b中访问一个变量的时候,搜索顺序是先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依 次查找,直到找到为止。如果整个作用域链上都无法找到,则返回undefined。如果函数b存在prototype原型对象,则在查找完自身的活动对象 后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。
四、闭包的应用场景
1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了 i的安全性。
2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
以上两点是闭包最基本的应用场景,很多经典案例都源于此。
五、Javascript的垃圾回收机制
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。
jQuery片段:
1 var
2 // Will speed up references to window, and allows munging its name.
3 window = this,
4 // Will speed up references to undefined, and allows munging its name.
5 undefined,
6 // Map over jQuery in case of overwrite
7 _jQuery = window.jQuery,
8 // Map over the $ in case of overwrite
9 _$ = window.$,
10
11 jQuery = window.jQuery = window.$ = function( selector, context ) {
12 // The jQuery object is actually just the init constructor 'enhanced'
13 return new jQuery.fn.init( selector, context );
14 },
15
16 // A simple way to check for HTML strings or ID strings
17 // (both of which we optimize for)
18 quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
19 // Is it a simple selector
20 isSimple = /^.[^:#\[\.,]*$/;
从这一节开始,我们剥掉jQuery的外衣,看看里面藏着些什么。前一节中曾经提及,如果单看外层的匿名函数,不看里面的实现的话,这个实现肯定不是闭包。但是,如果把jQuery的实现加上的话,这个肯定就是一种闭包应用。万丈高楼平地起,要了解闭包应用,就首先要了解它的基础。而这一节,我们遇到的片段,就是这个基础的所在——变量。(虽然这个片段包含众多知识点,但请容许我一个个慢慢分说。)
声明变量
变量的英文名为variable,其前三个字母正是我们在JS声明变量的关键字——var。那么,我们先来看一下如何去声明一个变量:
1 /*
2 * 声明变量的格式为
3 * var 变量名 初始化变量表达式列表(可选)
4 */
5 var a=1, b, c="test", d=a+b;// 虽然b还没有初始化,但是声明是合法的
6 alert(a);// "1"
7 alert(b);// "undefined"
8 alert(c);// "test"
9 alert(d);// "NaN"
10 alert(e);// 这里将引发编译错误:"e"未定义
11 // 同样地,如果在初始化中使用未定义的变量,也会引发编译错误。
如上例所示,声明变量需要使用var关键字,然后在空格后紧跟变量的名字。在声明变量的同时,我们也可以选择帮变量初始化。初始化的值可以是任何类型的值或表达式,但是,如果你尝试使用未定义的变量名来初始化,JS的编译器将会判定发生编译错误,并阻止程序继续往下运行。无论你是否对声明的变量进行初始化,你都可以继续声明第二个变量而无须使用var关键字。你所需要的只是运算符“,”。(关于运算符将在稍后章节详细讨论。)但当你没有对声明的变量进行初始化时,变量将会被赋予值“undefined”——undefined也是JS的固有类型之一。PS:JS中使用的运算符必须是半角的英文字符,甚至空格也一样。
重复声明的变量?!
当我们声明了一个变量,而又在后续的代码中再次对他进行声明,结果会怎么样呢?或许在很多其他语言中,这都会引起重复定义的错误,但在JS中,这完全是合法的。并且,由于JS是弱数据类型,所以变量能被赋予任何类型的值。请看以下例子:
1 var a=1;
2 alert(typeof a); // "number"
3 var a;
4 alert(typeof a); // "number"
5 var a="1";
6 alert(typeof a); // "string"
7 a=new String("1");
8 alert(typeof a); // "object"
看完上面的例子,你可能会产生两个疑问:
a)为什么第二个a还是number?
b)为什么第四个a是object?
为了解答第一个问题,我们首先要了解声明一个变量到底是怎么运作的。而第二个问题,我们将他放到下一节再讨论。
var 变量声明的工作步骤
当我们使用var关键字去声明变量的时候,JS解释器将会进行如下操作:
1)预编译javascript代码块中所有非函数块内的var关键字;
2)生成变量名标识并在其所在作用域分配空间;
3)按代码顺序运行至第一个var关键字所在行;
4)按变量声明列表表达式次序计算初始化表达式的值;
5)每计算完一条初始化表达式,就将其计算结果赋予给对应的声明变量;
6)继续运行后续代码至下一var关键字;
7)重复4-7步到代码块结束;
8)继续编译运行下一个代码块。
PS:JS将以一个代码块,也就是一个script标签为单位去运行一段JS代码。
正是因为var的工作方式,实际上程序执行时,解释器是根本看不到var关键字的。他执行的只是初始化表达式的赋值语句而已——所以问题a的答案就是例子中的第三句实际上什么事也没有做。所以,你一点也不用为代码中会否出现重复定义的变量名而烦恼。你真正需要担心的是,初始化语句所产生的变量的值的变化是否如你预期。除此之外,请不要尝试使用保留字作为变量名。这几乎在所有语言中都必须遵循的规范。
另外,在函数块中声明变量的工作步骤也是类似的,但不同的是,他们是在函数运行时才创建的。
没有var的变量声明?!
很多朋友都应该有这个经验——“我们根本不需要使用var来声明变量也能直接赋值啊!”。这是因为JS解释器在遇到赋值表达式的时候,会先在作用域链中寻找这个变量是否已经声明。如果这个变量没有声明,则隐式强制为其在全局(Global)作用域中声明,并将表达式的值赋予给该变量。
但究竟为什么会这样呢?其实一切都源自于变量的获取规则和作用域链的化合作用外加赋值运算符的催化作用。
作用域链
每个运行时的上下文都有与其对应的一个作用域。而作用域链正是把这些作用域连接起来的桥梁。它的作用与程序寻找某一变量标识有关:
1)JS解释器会按调用的顺序把作用域加进作用域链(像栈般早进入的作用域会在作用域链的底部);
2)然后在程序寻找某一变量标识时进入作用域链中的第一个索引,并在其中寻找该变量标识;
3)如果没有找到该标识,则前往下一个索引继续寻找;
4)如果已经找到该标识,则将该标识及其值返回;
5)当搜索到最后一个索引仍未能找到该标识,则在最后的索引上创建该标识,并使其值为null,最后返回该标识与值。
PS:而上述的第5步发生的前提是该标识处于赋值运算符表达式左侧。
因此,当你没有使用var声明变量而直接使用对该变量作初始化操作(简单赋值)时,JS会自动为你创建该空值标识,并让它可以顺利执行赋值语句。
变量与作用域链
从上面的描述,我们可以很轻易的看到变量与作用域链的关系。因为只要程序需要寻找变量,就必须通过作用域链。而前面所谈及的闭包问题正是由此而来的。回想一下我们前面的示例代码:
1 var abc=function(y){
2 var x=y;// 这个是局部变量
3 return function(){
4 alert(x++);// 就是这里调用了闭包特性中的一级函数局部变量的x,并对它进行操作
5 alert(y--);// 引用的参数变量也是自由变量
6 }}(5);// 初始化
7 abc();// "5" "5"
8 abc();// "6" "4"
9 abc();// "7" "3"
10 alert(x);// 报错!“x”未定义!
摘要: jQuery片段:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> 1 var
2 // Will spee... 阅读全文
|