蓝晶石

BlogJava 首页 新随笔 联系 聚合 管理
  10 Posts :: 0 Stories :: 5 Comments :: 0 Trackbacks

2005年8月10日 #

一. 什么是AJAX?

  这个名字代表了异步JavaScript+XMLHTTPRequest,并且意味着你可以在基于浏览器的JavaScript和服务器之间建立套接字通讯。其实AJAX并不是一种新技术,而是已经成功地用于现代浏览器中的若干成功技术的可能性组合。所有的AJAX应用程序实现了一种“丰富的”UI——这是通过JavaScript操作HTML文档对象模型并且经由XMLHttpRequest实现的精确定位的数据检索来实现的。典型的示例AJAX应用程序是Google Labs(http://labs.google.com)的Google Maps和Google Suggest。这些应用程序现场监视用户输入并且提供实时的页面更新。最重要的是,在用户通过地图导航或输入一个查找字符串的同时,这些事件不需要刷新页面。

  事实上,支持这些令人感到惊讶的应用的技术已经出现一段时间了,尽管它们要求复杂的技能以及使用浏览器的技巧。一些专利产品就提供了相似的能力——如Macromedia Flash插件,Java Applets或.NET运行时——在达到实用上已经有一段时间了。把一种可与服务器通话的脚本组件引入到浏览器中的思想早在IE 5.0中就已经存在。Firefox和其它流行的浏览器也加入到浏览器大军中并以一种内置对象形式支持XMLHTTPRequest。随着跨平台浏览器的出现,这些技术得到了认可并在2004年3月一家称为Adaptive Path的公司中正式提出了AJAX。

  简而言之,由于来自于Google的支持和安装了一点可用的浏览器技术,加上为了一种"更好的用户体验",每个人都在把客户端技术添加到Web应用程序上。

  二. AJAX与传统应用程序的区别

  一个传统Web应用程序模型实际上是一种基本的事件——用户被迫提交表单以实现页面交换。也就是说,表单提交和页面传送无法得到保证:还有更坏的情形——用户需要再次点击。这与AJAX截然不同-——数据跨过线路而不是完整的HTML页面传输。这种数据交换是经由特定的浏览器对象:XMLHttpRequest实现的;再由适当的逻辑来处理每个数据请求的结果,页面的特定区域而不是完整的页面被更新。结果是更快的速度,更少的拥挤和更好的信息传送控制。

  传统型"click-refresh"Web应用程序强迫用户中断工作过程而等待页面的重装。通过引入AJAX技术,一个客户端脚本能够异步地与服务器通话,而用户仍能保持输入数据。除了对用户透明之外,这样的异步意味着服务器可以有更多时间来处理请求。

  传统Web应用程序把所有的处理代理到服务器并且强迫服务器进行状态管理。AJAX允许灵活划分应用程序逻辑以及客户和服务器之间的状态管理。这就消除了一种"click-refresh"依赖性并且提供更好的服务器可伸缩性。当该状态存储在客户端,你就不必跨越服务器来维持会话或保存/结束状态-其使用期限是由客户端来定义的。

  三. AJAX——分布式的MVC

  尽管AJAX应用程序依靠JavaScript来实现描述层,然而处理能力和知识库仍然存在于服务器上。此时,AJAX应用程序大量的与J2EE服务器通讯——把数据输入/输出Web服务和servlets。具有基于AJAX的描述层的J2EE应用程序和标准J2EE应用程序之间的区别首先在于,MVC是通过线路分布的。通过使用AJAX,视图是本地的,而模型和控制器是分布式的——这使得开发者能够灵活地决定哪些部件会是基于客户端的。具体地说,本地视图通过巧妙地操作HTML DOM而生成图形;控制器局部地处理用户输入并且根据开发者的判断扩展到服务器的处理——经由HTTP请求(Web服务,XML/RPC或其它)实现;模型的远程部分是根据客户端需要而下载的以达到实时更新客户端页面;并且状态是在客户端收集的。

  在以后的AJAX文章中,我们将比较深入地讨论这里的每一种组件并提供有关它们联合在一起进行应用的示例。现在,先不多说,让我们详细地分析一个简单的AJAX示例。

  四. 邮政区号校验和查询

  我们将创建一个包含三个INPUT字段(Zip,City和State)的HTML页面。我们将保证,只要用户输入邮政区号的前三个数字,该页面上的字段就会用第一个匹配的状态值填充。一旦用户输入了所有五位邮政区号数,我们将立即决定和填充相应的城市。如果邮政区号无效(在服务器的数据库没有找到),那么我们将把邮政区号的边界设置为红色。这样的可视化线索有助于用户并且在现代浏览器中已经成为一种标准(作为一实例,当Firefox找到一个HTML页面中的匹配关键字时,它会高亮与你在浏览器查找域输入的内容一致的部分)。

  让我们首先创建一个简单的包含三个INPUT字段的HTML:zip,city和state。请注意,一旦一个字符输入进邮政区号字段域中,即调用方法zipChanged()。JavaScript函数zipChanged()(见下)在当zip长度为3时调用函数updateState(),而在当zip长度为5时调用函数up-dateCity()。而updateCity()和updateState()把大部分的工作代理到另一个函数ask()。

Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()"
style="width:60"/>
City: <input id="city" disabled maxlength="32" style="width:160"/>
State:<input id="state" disabled maxlength="2" style="width:30"/>
<script src="xmlhttp.js"></script>
<script>
var zipField = null;
function zipChanged(){
zipField = document.getElementById("zipcode")
var zip = zipField.value;
zip.length == 3?updateState(zip):zip.length == 5?updateCity(zip):"";
}
function updateState(zip) {
 var stateField = document.getElementById("state");
 ask("resolveZip.jsp?lookupType=state&zip="+zip, stateField, zipField);
}
function updateCity(zip) {
 var cityField = document.getElementById("city");
 ask("resolveZip.jsp? lookupType=city&zip="+zip, cityField, zipField);
}
</script>


    函数ask()与服务器进行通讯并分配一个回调函数来处理服务器的响应(见下列代码)。后面,我们将分析具有双重特点的resolveZip.jsp的内容-它根据zip字段中的字符数查找city或state信息。重要的是,ask()使用了具有异步特点的XmlHttpRequest,这样填充state和city字段或着色zip字段边界就可以不必减慢数据入口而得以实现。首先,我们调用request.open()-它用服务器打开套接字频道,使用一个HTTP动词(GET或POST)作为第一个参数并且以数据提供者的URL作为第二个参数。request.open()的最后一个参数被设置为true-它指示该请求的异步特性。注意,该请求还没有被提交。随着对request.send()的调用,开始提交-这可以为POST提供任何必要的有效载荷。在使用异步请求时,我们必须使用request.onreadystatechanged属性来分配请求的回调函数。(如果请求是同步的话,我们应该能够在调用request.send之后立即处理结果,但是我们也有可能阻断用户,直到该请求完成为止。)

HTTPRequest = function () {
 var xmlhttp=null;
 try {
  xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
 } catch (_e) {
  try {
   xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
  } catch (_E) { }
 }
 if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
  try {
   xmlhttp = new XMLHttpRequest();
  } catch (e) {
   xmlhttp = false;
  }
 }
 return xmlhttp;
}

function ask(url, fieldToFill, lookupField) {
 var http = new HTTPRequest();
 http.open("GET", url, true);
 http.onreadystatechange = function (){ handleHttpResponse(http, fieldToFill,lookupField)};
 http.send(null);
}

function handleHttpResponse(http, fieldToFill, lookupField) {
 if (http.readyState == 4) {
  result = http.responseText;
  if ( -1 != result.search("null") ) {
   lookupField.style.borderColor = "red";
   fieldToFill.value = "";
  } else {
   lookupField.style.borderColor = "";
   fieldToFill.value = result;
  }
 }
}

  为ask()所使用的HttpRequest()函数(见上)是一跨浏览器的XMLHTTPRequest的一个实例的构造器;稍后我们将分析它。到目前为止,请注意对于handleResponse()的调用是如何用一匿名函数包装的-这个函数是function(){handleHttpResponse(http,fieldToFill, lookupField)}。

  该函数的代码是动态创建的并且在每次我们给http.onreadstatechange属性赋值时被编译。结果,JavaScript创建一个指向上下文(所有的变量都可以存取正在结束的方法-ask())的指针。这样以来,匿名函数和handleResponse()就能够被保证充分存取所有的上下文宿主的变量,直至到匿名函数的参考被垃圾回收站收集为止。换句话说,无论何时我们的匿名函数被调用,它都能无缝地参考request,fieldToFill和lookupField变量,就象它们是全局的一样。而且,每次ask()调用都将创建环境的一个独立拷贝,并且此时这些变量中保存有该函数将结束时的值。

  现在,让我们分析一下函数handleResponse()。既然它能够在请求处理的不同状态下激活,那么该函数将忽略所有的情形-除了该请求处理完成之外-这相应于request.readyState属性等于4("Completed")。此时,该函数读取服务器的响应文本。与它的名字所暗示的相反,XmlHttpRequest的输入和输出都不必限于XML格式。特别地,我们的resolveZip.jsp(见源码中的列表1)返回普通文本。如果返回值为"unknown",那么该函数将假定邮政区号是无效的并且把查找字段(zip)边界颜色置为红色。否则,返回值被用于填充字段state或city,并且zip的边界被赋予一种缺省颜色。

XMLHttpRequest-传输对象

  让我们返回到我们的XMLHTTPRequest的跨浏览器实现。最后一个列表包含一个HttpRequest()函数-它向上兼容于IE5.0和Mozilla 1.8/FireFox。为简化起见,我们只创建一个微软XMLHTTPRequest对象,而且如果创建失败,我们假定它是Firefox/Mozilla。

  该函数的核心是XMLHTTPRequest-这是一个本机浏览器对象,它为包括HTTP协议的任何东西与服务器之间的通讯提供方便。它允许指定任何HTTP动词,头部和有效载荷,并且能够以异步或同步方式工作。不需要下载也不需要安装任何插件-尽管在IE的情形下,XMLHTTPRequest是一个集成到浏览器内部的ActiveX。因而,"Run ActiveX Control and Plugins"默认IE权限应该正好适合使用它。

  最重要的是,XMLHTTPRequest允许一个到服务器的RPC风格的编程查询而不需要任何页面刷新。它以一种可预测的,可控制的方式来实现此-提供了到HTTP协议的所有细节的完整存取-包括头部和数据的任何定制格式。在以后的文章中,我们将向你展示其它一些业界协议-你可以在这些传输协议(如Web服务和XML-RPC)之上运行-它们极大地简化大规模应用程序的开发和维护。

  五.服务器端逻辑

  最后,服务器端的resolveZip.jsp被从函数ask()中调用(见所附源码中的列表1)。这个resolveZip.jsp在两种由当前的邮政区号长度所区分的独立的场所下被调用(见zipChanged()函数)。请求参数lookupType的值或者是state或者是city。为简化起见,我们将假定,两个文件state.properties和city.properties都位于服务器中C驱动器的根目录下。resolveZip.jsp逻辑负责用适当的预装载的文件返回查找值。

  我们的支持AJAX的页面现在已经准备好了。

  六.远程脚本技术-一种可选方法

  一些更旧的AJAX实现是基于所谓的远程脚本技术。这种思想是,用户的行为导致经由IFRAME对服务器进行查询,而服务器用JavaScript作出响应,该脚本一旦到达客户端立即被执行。这与XMLHttpRequest方法相比存在较大的区别,在后者情况下,服务器响应数据而客户端解释数据。其好处是这种解决方案支持更旧的浏览器。

  基于IFRAME示例的HTML部分(见所附源码中的列表2)与我们在XMLHTTPRequest场合下所用的极相似,但是这次我们将引入另外一个IFRAME元素-controller:

Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()"
style="width:60" size="20"/>
City: <input id="city" disabled maxlength="32" style="width:160" size="20"/>
State:<input id="state" disabled maxlength="2" style="width:30" size="20"/>
<iframe id="controller" style="visibility:hidden;width:0;height:0"></iframe>

  我们保持每次击键都调用zipChanged()一次,但是这一次,从zipChanged()中被调用的函数ask()(见所附源码中的列表3)负责设置IFRAME的src属性,而不是调用一个XMLHTTPRequest:

function ask(url, fieldToFill, lookupField){
 var controller = document.getElementById("controller");
 controller.src= url+"&field="+fieldToFill.id+"&zip="+lookupField.id;
}

  服务器端逻辑由一个粗略的resolveZip.jsp(见所附源码中的列表4)所描述。它与它的XMLHTTPRequest对应物相区别-它返回JavaScript语句,这些语句设置变量字段lookup和city的全局值,而且一旦它到达浏览器即从全局窗口的执行上下文中调用函数response()。

  函数response()是一修改版本的handleResponse()-这一函数可以免于处理未完成的请求(详见本文所附源码中的列表2)。

  七. 难题

  为简化起见,让我们"俯看"一下在我们的示例代码中的一些重要的问题:

  1.事实-XMLHTTPRequest对象实例和回调函数调用在被使用以后并没被破坏-在每次调用后这有可能导致内存泄漏。适当编写的代码应该破坏或重用对象池中的这些实例。而且,客户端必须使用与服务器软件相同的对象管理技术。

  2.在大多数情况下,错误往往得不到有效处理。例如,在方法ask()中对request.open()的调用可能引发一个异常,这是必须要捕获和处理的,即使在浏览器中没有设置JavaScript异常自动捕获功能。而handleResponse()函数又是另外一个例子。它必须要为可能的服务器端和通讯错误而检查headers和responseText值。在发生错误的情况下,它必须尽力恢复并/或者报告错误。正确开发的AJAX应用程序要尽可能避免"提交"松散的数据,因为往往存在线路断开和其它低级通讯的问题-所以这些程序必须建立一个强壮的和自恢复的框架为此提供支持。

  3.当前服务器端框架提供相当多的功能-它们可以与一种自由刷新方法和谐相处。例如,让我们考虑一个定制的在指定时间内的服务器端认证的问题。在这种情况下,我们必须拦截到XMLHTTPRequest调用的安全系统响应,显示登录屏幕,然后在用户被认证后重新发出请求。

  所有的这些问题只是一些典型的用低级API工作的任何应用程序代码,而且所有这些问题都能被解决。好消息是,解决这些问题所需要的技术十分相似于大多数Java开发技术,如Web服务,定制标签和XML/XSLT。唯一的区别在于,现在这些技术以下列形式用于客户端:

  ·Web服务-使用SOAP/REST/RPC等简单通讯标准

  ·客户端定制标签-打包丰富的客户端控件并集成AJAX功能

  ·数据操作-基于XML和基于XSLT技术

  八. 小结

  AJAX方法能够向人们提供一种与桌面应用程序相同的丰富的互联网体验。但是,我们必须有选择地使用AJAX技术,如当你仍在线购物时,你绝对不想让你的信用卡通过后台处理就悄悄地开始付款。AJAX会成为一种持续的动力吗?我们当然希望这样。在过去的五年时间内我们一直在努力开发AJAX应用程序并且能证明它是健全并且很有效的。然而,它要求一个开发者必须精通大量技术而不是在传统的"click-refresh"Web应用程序中所使用的那些。

posted @ 2005-12-29 17:10 kyanite 阅读(212) | 评论 (0)编辑 收藏

与任何技术一样,使用Ajax在相当多的方面都可能范错误。我在这儿讨论的问题目前都缺少解决方案,并将会随着Ajax的成熟而解决或提高。随着开发 Ajax应用经验的不断获取,开发者社区中将会出现最好的实践经验与指导方针。

  1、XMLHttpRequest的有效性

  Ajax开发者面对的一个最大问题是当XMLHttpRequest不可用时如何反应。虽然大部分现代浏览器支持XMLHttpRequest,但还是有少量的用户,他们的浏览器不能支持,或由于浏览器安全设置而阻止对XMLHttpRequest的使用。若你的Web应用发布于公司内部的 Intranet上,你很可能可以指定支持哪种浏览器,并可以确保XMLHttpRequest是可用的。若你在公共WEB上发布,则你必须意识到由于假定XMLHttpRequest是可用的,所有就阻止了老浏览器、手持设备浏览器等等用户来使用你的系统。

  然而,你应该尽力保证应用系统“正常降级”使用,在系统中保留适用于不支持XMLHttpRequest的浏览器的功能。在购物车例子中,最好的方法是有一个Add to Cart按钮,可以进行常规的提交处理,并刷新页面来反映购物车状态的变化。Ajax行卫可以在页面被载入时通过JavaScript添加到页面中,只在 XMLHttpRequest可用的情况下,为每个Add to Cart按钮加上JavaScript处理函数。另一个方法是在用户登录时检测XMLHttpRequest,再决定是提供Ajax版本还是常规基于 form提交的版本。

  2、可用性考虑

  围绕着Ajax应用的大部分问题都是很普通的问题。例如,让用户知道他们的输入已经被注册并处理,是很重要的,因为在XMLHttpRequest处理过程中并不能提供通常的漏斗旋转光标。一种方法是将“确认”按扭上的文本替换为“正在更新中…”,以避免用户在等待响应时多次点击按钮。

  另一个问题是,用户可能没有注意到他们正在观看的页面已经被更新。可以通过使用各种视觉技巧来将用户的眼光吸引到页面的更新区域。还有一个问题是通过 Ajax更新页面打断了浏览器“退回前页”按钮的正常工作,地址栏中的URL不能反映页面的全部状态,并且不能使用书签功能。参见Resource章节中列出的网站地址上的文章来了解更多Ajax应用关于可用性方面的问题。

  3、服务器负载

  使用Ajax界面代替传统的基于form的界面可能戏剧性地增加传递到服务器的请求数量。例如,一个普通的Google搜索给服务器造成一次命中,并在用户确认搜索表单时发生。然而,Google Suggest,将会试图自动完成你的搜索词,在用户打字时将会往服务器发送多个请求。在开发一个Ajax应用时,要注意到你将会发送多少请求到用户器端,以及服务器的负载指标。你可以通过在客户端适当地缓存请求、与服务器响应来缓减负载压力。你也应该在设计Ajax应用时尽量在客户端处理更多的逻辑,而不用与服务器端通讯。

  4、处理异步

  一定要记住,没有任何东西可以保证XMLHttpRequest将会按照它们被发送的顺序来依次结束。实际上,你在设计系统时,脑子里应该始终假定它们不会按原来顺序结束。在购物车例子中,使用了一个最后更新的时间戳来保证最新的数据不会被改写。这个非常基本的方法可以在购物车场景中工作,但可能不能在其它情况下工作。在设计时刻就要考虑你该如何处理异步服务器响应。

  结论

  你现在应该对于Ajax的基本原则有了一个良好的了解,另外,你应该理解一些更高级的随Ajax方法而来的设计问题。创建一个成功的Ajax应用需要一系列的方法—从JavaScript UI设计到服务器端架构—但是你现在应该已经具备了需要使用到的Ajax核心知识。

posted @ 2005-12-29 16:01 kyanite 阅读(165) | 评论 (0)编辑 收藏

引言

Java的堆是一个运行时数据区,类的实例(对象)从中分配空间。Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,这些对象通过newnewarrayanewarraymultianewarray等指令建立,但是它们不需要程序代码来显式地释放。一般来说,堆的是由垃圾回收 来负责的,尽管JVM规范并不要求特殊的垃圾回收技术,甚至根本就不需要垃圾回收,但是由于内存的有限性,JVM在实现的时候都有一个由垃圾回收所管理的堆。垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。

垃圾收集的意义

C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块。垃圾收集意味着程序不再需要的对象是"无用信息",这些信息将被丢弃。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。事实上,除了释放没用的对象,垃圾收集也可以清除内存记录碎片。由于创建对象和垃圾收集器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。

垃圾收集能自动释放内存空间,减轻编程的负担。这使Java 虚拟机具有一些优点。首先,它能使编程效率提高。在没有垃圾收集机制的时候,可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程的时候,靠垃圾收集机制可大大缩短时间。其次是它保护程序的完整性, 垃圾收集是Java语言安全性策略的一个重要部份。

垃圾收集的一个潜在的缺点是它的开销影响程序性能。Java虚拟机必须追踪运行程序中有用的对象, 而且最终释放没用的对象。这一个过程需要花费处理器的时间。其次垃圾收集算法的不完备性,早先采用的某些垃圾收集算法就不能保证100%收集到所有的废弃内存。当然随着垃圾收集算法的不断改进以及软硬件运行效率的不断提升,这些问题都可以迎刃而解。

垃圾收集的算法分析

Java语言规范没有明确地说明JVM使用哪种垃圾回收算法,但是任何一种垃圾收集算法一般要做2件基本的事情:(1)发现无用信息对象;(2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。

大多数垃圾回收算法使用了根集(root set)这个概念;所谓根集就量正在执行的Java程序可以访问的引用变量的集合(包括局部变量、参数、类变量),程序可以使用引用变量访问对象的属性和调用对象的方法。垃圾收集首选需要确定从根开始哪些是可达的和哪些是不可达的,从根集可达的对象都是活动对象,它们不能作为垃圾被回收,这也包括从根集间接可达的对象。而根集通过任意路径不可达的对象符合垃圾收集的条件,应该被回收。下面介绍几个常用的算法。

1、  引用计数法(Reference Counting Collector)

引用计数法是唯一没有使用根集的垃圾回收的法,该算法使用引用计数器来区分存活对象和不再使用的对象。一般来说,堆中的每个对象对应一个引用计数器。当每一次创建一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了作用域后(该对象丢弃不再使用),引用计数器减1,一旦引用计数器为0,对象就满足了垃圾收集的条件。

基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,适宜地必须 实时运行的程序。但引用计数器增加了程序执行的开销,因为每次对象赋给新的变量,计数器加1,而每次现有对象出了作用域生,计数器减1

2tracing算法(Tracing Collector)

tracing算法是为了解决引用计数法的问题而提出,它使用了根集的概念。基于tracing算法的垃圾收集器从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象,例如对每个可达对象设置一个或多个位。在扫描识别过程中,基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器.

3compacting算法(Compacting Collector)

为了解决堆碎片问题,基于tracing的垃圾回收吸收了Compacting算法的思想,在清除的过程中,算法将所有的对象移到堆的一端,堆的另一端就变成了一个相邻的空闲内存区,收集器会对它移动的所有对象的所有引用进行更新,使得这些引用在新的位置能识别原来 的对象。在基于Compacting算法的收集器的实现中,一般增加句柄和句柄表。  

4copying算法(Coping Collector)

该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于coping算法的垃圾 收集就从根集中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。

一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象面和空闲区域面,在对象面与空闲区域面的切换过程中,程序暂停执行。

5generation算法(Generational Collector)
  stop-and-copy垃圾收集器的一个缺陷是收集器必须复制所有的活动对象,这增加了程序等待时间,这是coping算法低效的原因。在程序设计中有这样的规律:多数对象存在的时间比较短,少数的存在时间比较长。因此,generation算法将堆分成两个或多个,每个子堆作为对象的一代(generation)。由于多数对象存在的时间比较短,随着程序丢弃不使用的对象,垃圾收集器将从最年轻的子堆中收集这些对象。在分代式的垃圾收集器运行后,上次运行存活下来的对象移到下一最高代的子堆中,由于老一代的子堆不会经常被回收,因而节省了时间。

6adaptive算法(Adaptive Collector)

在特定的情况下,一些垃圾收集算法会优于其它算法。基于Adaptive算法的垃圾收集器就是监控当前堆的使用情况,并将选择适当算法的垃圾收集器。

透视Java垃圾回收

1、命令行参数透视垃圾收集器的运行

2、使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。在命令行中有一个参数-verbosegc可以查看Java使用的堆内存的情况,它的格式如下:

java -verbosegc classfile

  可以看个例子:

class TestGC
{
 public static void main(String[] args)
 {
  new TestGC();
  System.gc();
  System.runFinalization();
 }
}


  在这个例子中,一个新的对象被创建,由于它没有使用,所以该对象迅速地变为可达,程序编译后,执行命令: java -verbosegc TestGC 后结果为:

[Full GC 168K->97K(1984K), 0.0253873 secs]

  机器的环境为,Windows 2000 + JDK1.3.1,箭头前后的数据168K97K分别表示垃圾收集GC前后所有存活对象使用的内存容量,说明有168K-97K=71K的对象容量被回收,括号内的数据1984K为堆内存的总容量,收集所需要的时间是0.0253873秒(这个时间在每次执行的时候会有所不同)。

  2finalize方法透视垃圾收集器的运行

  在JVM垃圾收集器收集一个对象之前 ,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止化该对象心释放资源,这个方法就是finalize()。它的原型为:

protected void finalize() throws Throwable

  在finalize()方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable表示它可以抛出任何类型的异常。

  之所以要使用finalize(),是由于有时需要采取与Java的普通方法不同的一种方法,通过分配内存来做一些具有C风格的事情。这主要可以通过"固有方法"来进行,它是从Java里调用非Java方法的一种方式。CC++是目前唯一获得固有方法支持的语言。但由于它们能调用通过其他语言编写的子程序,所以能够有效地调用任何东西。在非Java代码内部,也许能调用Cmalloc()系列函数,用它分配存储空间。而且除非调用了free(),否则存储空间不会得到释放,从而造成内存"漏洞"的出现。当然,free()是一个CC++函数,所以我们需要在finalize()内部的一个固有方法中调用它。也就是说我们不能过多地使用finalize(),它并不是进行普通清除工作的理想场所。

  在普通的清除工作中,为清除一个对象,那个对象的用户必须在希望进行清除的地点调用一个清除方法。这与C++"破坏器"的概念稍有抵触。在C++中,所有对象都会破坏(清除)。或者换句话说,所有对象都"应该"破坏。若将C++对象创建成一个本地对象,比如在堆栈中创建(在Java中是不可能的),那么清除或破坏工作就会在"结束花括号"所代表的、创建这个对象的作用域的末尾进行。若对象是用new创建的(类似于Java),那么当程序员调用C++delete命令时(Java没有这个命令),就会调用相应的破坏器。若程序员忘记了,那么永远不会调用破坏器,我们最终得到的将是一个内存"漏洞",另外还包括对象的其他部分永远不会得到清除。

  相反,Java不允许我们创建本地(局部)对象--无论如何都要使用new。但在Java中,没有"delete"命令来释放对象,因为垃圾收集器会帮助我们自动释放存储空间。所以如果站在比较简化的立场,我们可以说正是由于存在垃圾收集机制,所以Java没有破坏器。然而,随着以后学习的深入,就会知道垃圾收集器的存在并不能完全消除对破坏器的需要,或者说不能消除对破坏器代表的那种机制的需要(而且绝对不能直接调用finalize(),所以应尽量避免用它)。若希望执行除释放存储空间之外的其他某种形式的清除工作,仍然必须调用Java中的一个方法。它等价于C++的破坏器,只是没后者方便。

  下面这个例子向大家展示了垃圾收集所经历的过程,并对前面的陈述进行了总结。

class Chair {
 static boolean gcrun = false;
 static boolean f = false;
 static int created = 0;
 static int finalized = 0;
 int i;
 Chair() {
  i = ++created;
  if(created == 47)
   System.out.println("Created 47");
 }
 protected void finalize() {
  if(!gcrun) {
   gcrun = true;
   System.out.println("Beginning to finalize after " + created + " Chairs have been created");
  }
  if(i == 47) {
   System.out.println("Finalizing Chair #47, " +"Setting flag to stop Chair creation");
   f = true;
  }
  finalized++;
  if(finalized >= created)
   System.out.println("All " + finalized + " finalized");
 }
}

public class Garbage {
 public static void main(String[] args) {
  if(args.length == 0) {
   System.err.println("Usage: \n" + "java Garbage before\n or:\n" + "java Garbage after");
   return;
  }
  while(!Chair.f) {
   new Chair();
   new String("To take up space");
  }
  System.out.println("After all Chairs have been created:\n" + "total created = " + Chair.created +
", total finalized = " + Chair.finalized);
  if(args[0].equals("before")) {
    System.out.println("gc():");
    System.gc();
    System.out.println("runFinalization():");
    System.runFinalization();
  }
  System.out.println("bye!");
  if(args[0].equals("after"))
   System.runFinalizersOnExit(true);
 }
}


  上面这个程序创建了许多Chair对象,而且在垃圾收集器开始运行后的某些时候,程序会停止创建Chair。由于垃圾收集器可能在任何时间运行,所以我们不能准确知道它在何时启动。因此,程序用一个名为gcrun的标记来指出垃圾收集器是否已经开始运行。利用第二个标记fChair可告诉main()它应停止对象的生成。这两个标记都是在finalize()内部设置的,它调用于垃圾收集期间。另两个static变量--created以及finalized--分别用于跟踪已创建的对象数量以及垃圾收集器已进行完收尾工作的对象数量。最后,每个Chair都有它自己的(非staticint i,所以能跟踪了解它具体的编号是多少。编号为47Chair进行完收尾工作后,标记会设为true,最终结束Chair对象的创建过程。(关于这个例子的更具体的分析和说明请参看《Java编程思想》的第四章)

  关于垃圾收集的几点补充

  经过上述的说明,可以发现垃圾回收有以下的几个特点:

  (1)垃圾收集发生的不可预知性:由于实现了不同的垃圾收集算法和采用了不同的收集机制,所以它有可能是定时发生,有可能是当出现系统空闲CPU资源时发生,也有可能是和原始的垃圾收集一样,等到内存消耗出现极限时发生,这与垃圾收集器的选择和具体的设置都有关系。

  (2)垃圾收集的精确性:主要包括2 个方面:(a)垃圾收集器能够精确标记活着的对象;(b)垃圾收集器能够精确地定位对象之间的引用关系。前者是完全地回收所有废弃对象的前提,否则就可能造成内存泄漏。而后者则是实现归并和复制等算法的必要条件。所有不可达对象都能够可靠地得到回收,所有对象都能够重新分配,允许对象的复制和对象内存的缩并,这样就有效地防止内存的支离破碎。(3)现在有许多种不同的垃圾收集器,每种有其算法且其表现各异,既有当垃圾收集开始时就停止应用程序的运行,又有当垃圾收集开始时也允许应用程序的线程运行,还有在同一时间垃圾收集多线程运行。

  (4)垃圾收集的实现和具体的JVM 以及JVM的内存模型有非常紧密的关系。不同的JVM 可能采用不同的垃圾收集,而JVM 的内存模型决定着该JVM可以采用哪些类型垃圾收集。现在,HotSpot 系列JVM中的内存系统都采用先进的面向对象的框架设计,这使得该系列JVM都可以采用最先进的垃圾收集。

  (5)随着技术的发展,现代垃圾收集技术提供许多可选的垃圾收集器,而且在配置每种收集器的时候又可以设置不同的参数,这就使得根据不同的应用环境获得最优的应用性能成为可能。

  针对以上特点,我们在使用的时候要注意:

  (1)不要试图去假定垃圾收集发生的时间,这一切都是未知的。比如,方法中的一个临时对象在方法调用完毕后就变成了无用对象,这个时候它的内存就可以被释放。

  (2Java中提供了一些和垃圾收集打交道的类,而且提供了一种强行执行垃圾收集的方法--调用System.gc(),但这同样是个不确定的方法。Java 中并不保证每次调用该方法就一定能够启动垃圾收集,它只不过会向JVM发出这样一个申请,到底是否真正执行垃圾收集,一切都是个未知数。

  (3)挑选适合自己的垃圾收集器。一般来说,如果系统没有特殊和苛刻的性能要求,可以采用JVM的缺省选项。否则可以考虑使用有针对性的垃圾收集器,比如增量收集器就比较适合实时性要求较高的系统之中。系统具有较高的配置,有比较多的闲置资源,可以考虑使用并行标记/清除收集器。

  (4)关键的也是难把握的问题是内存泄漏。良好的编程习惯和严谨的编程态度永远是最重要的,不要让自己的一个小错误导致内存出现大漏洞。

  (5)尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为null,暗示垃圾收集器来收集该对象,还必须注意该引用的对象是否被监听,如果有,则要去掉监听器,然后再赋空值。

  结束语

  一般来说,Java开发人员可以不重视JVM中堆内存的分配和垃圾处理收集,但是,充分理解Java的这一特性可以让我们更有效地利用资源。同时要注意finalize()方法是Java的缺省机制,有时为确保对象资源的明确释放,可以编写自己的finalize方法。

posted @ 2005-12-27 12:10 kyanite 阅读(232) | 评论 (0)编辑 收藏

      近日,使用struts 1.1,发现讨厌的中文乱码问题,在form的传送过程和入库时候出现。就我在网络上找的方法罗列如下:
(Tomcat 5.0.28+struts 1.1+hibernate 2.1+sqlserver2k)
1.直接转编码public static String isoToGB(String src){   
String strRet=null;   
try{    
  strRet = new String(src.getBytes("ISO_8859_1"),"GB2312");  
  }catch(Exception e)    {         
}    return strRet;
}通过一个函数转编码,我没有成功,不知为何!

2.过滤filter设置法

package yourbean;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

public class servfilter extends HttpServlet implements Filter {  private FilterConfig filterConfig;  //Handle the passed-in FilterConfig  public void init(FilterConfig filterConfig) {    this.filterConfig = filterConfig;  }  //Process the request/response pair  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {    try {      request.setCharacterEncoding("GB2312");       ((HttpServletResponse)response).setHeader("Cache-control","no-cache");      response.setHeader("Pragma","No-cache"); response.setHeader("Cache-Control","no-cache"); response.setHeader("Expires","0");       ((HttpServletResponse)response).setHeader("Pragram","no-cache");      filterChain.doFilter(request, response);    }    catch(ServletException sx) {      filterConfig.getServletContext().log(sx.getMessage());    }    catch(IOException iox) {      filterConfig.getServletContext().log(iox.getMessage());    }  }  //Clean up resources  public void destroy() {  }}下面是一个web.xml文件你用jbuilder写上面的bean的时候会生成一个<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>  <display-name>Welcome to Tomcat</display-name>  <description>     Welcome to Tomcat  </description>  <filter>    <filter-name>servfilter</filter-name>    <filter-class>yourbean.servfilter</filter-class>  </filter>  <filter-mapping>    <filter-name>servfilter</filter-name>    <url-pattern>/*</url-pattern>  </filter-mapping></web-app>把上面的servfilter编译放在你的web-inf/classes/yourbean/下web.xml放在web-inf/下和classes在一个目录下在每个jsp页面上加上<%@page contentType="text/html;charset=GBK"%>

也不是很方便,而且在tomcat也没有成功,继续郁闷!

3.我现在使用方法,推荐!!

写一个myActionServlet来并覆盖ActionServlet中的process()方法。

  protected void process(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException {    /**@todo Override this org.apache.struts.action.ActionServlet method*/    request.setCharacterEncoding("GB2312");//就加着一行一切都解决了    super.process(request, response);  }

当然别忘了改一下web.xml里面的配置  <servlet>    <servlet-name>action</servlet-name>    <servlet-class>strutsdemo.myActionServlet</servlet-class>    <init-param>      <param-name>debug</param-name>      <param-value>2</param-value>    </init-param>    <init-param>      <param-name>config</param-name>      <param-value>/WEB-INF/struts-config.xml</param-value>    </init-param>    <load-on-startup>2</load-on-startup>  </servlet>

改一下servlet-class标签中的内容就可以!

真的可以,一劳用yi!

具体编码的理论就不说了,google上已经够多了。

另外,如果不用struts的话,hibernate也可能碰到中文乱码问题,只要在hibernate.cfg.xml配置中如下:

<property name="hibernate.connection.url">   jdbc:microsoft:sqlserver://Localhost:1433;SelectMethod=cursor;characterEncoding=GBK;DatabaseName=myDatabase.  </property>

characterEncoding=GBK!就可以了。
posted @ 2005-08-20 13:52 kyanite 阅读(679) | 评论 (0)编辑 收藏

 最近在公司里做了一个手机的项目,需要JAVA程序在发送短信的时候和第三方的短信服务器连接。短信接口是用C++写的。琢磨了三天,大致搞懂了JNI的主体部分。先将心得整理,希望各位朋友少走弯路。
    首先引用一篇文章,介绍一个简单的JNI的调用的过程。
    JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。 
    JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。 
    简单介绍及应用如下: 
    一、JAVA中所需要做的工作 
        在JAVA程序中,首先需要在类中声明所调用的库名称,如下: 
  1.         static { 
  2.                  System.loadLibrary(“goodluck”); 
  3.                }
 
        在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。 
        还需要对将要调用的方法做本地声明,关键字为native。并且只需要声明,而不需要具 体实现。如下: 
        public native static void set(int i); 
        public native static int get(); 
        然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。 
        例如程序testdll.java,内容为: 
  1.         public class testdll 
  2.          { 
  3.            static 
  4.                  { 
  5.                   System.loadLibrary("goodluck"); 
  6.                  } 
  7.            public native static int get(); 
  8.            public native static void set(int i); 
  9.            public static void main(String[] args) 
  10.                 { 
  11.                   testdll test = new testdll(); 
  12.                   test.set(10); 
  13.                   System.out.println(test.get()); 
  14.                  } 
  15.            }
 
    用javac testdll.java编译它,会生成testdll.class。 
    再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。 
    二、C/C++中所需要做的工作 
    对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。 
    接上例子。我们先看一下testdll.h文件的内容: 
    /* DO NOT EDIT THIS FILE - it is machine generated */ 
    #include <jni.h> 
    /* Header for class testdll */ 
    #ifndef _Included_testdll 
    #define _Included_testdll 
    #ifdef __cplusplus 
    extern "C" { 
    #endif 
     /* 
      * Class: testdll 
      * Method: get 
      * Signature: ()I 
      */ 
    JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 
      /* 
       * Class: testdll 
       * Method: set 
       * Signature: (I)V 
       */ 
    JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint); 
    #ifdef __cplusplus 
                } 
    #endif 
    #endif 
    在具体实现的时候,我们只关心两个函数原型 
    JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和 
    JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint); 
    这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。 
    好,下面我们用testdll.cpp文件具体实现这两个函数: 
    #include "testdll.h" 
    int i = 0; 
    JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass) 
      { 
       return i; 
      } 
    JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j) 
      { 
       i = j; 
      } 
    编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll 。把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。 
我的项目比较复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。
大体程序如下:
  1. public class SendSMS {
  2. static
  3.     {
  4.       System.out.println(System.getProperty("java.library.path"));
  5.       System.loadLibrary("sms");
  6.     }
  7.     public native static int SmsInit();
  8.     public native static int SmsSend(byte[] mobileNo, byte[] smContent);
  9. }

在这里要注意的是,path里一定要包含类库的路径,否则在程序运行时会抛出异常:
java.lang.UnsatisfiedLinkError: no sms in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)
         at java.lang.Runtime.loadLibrary0(Runtime.java:788)
         at java.lang.System.loadLibrary(System.java:834)
         at com.mobilesoft.sms.mobilesoftinfo.SendSMS.<clinit>(SendSMS.java:14)
    at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
Exception in thread "main"
指引的路径应该到.dll文件的上一级,如果指到.dll,则会报:
java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries
         at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)
         at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)
         at java.lang.Runtime.loadLibrary0(Runtime.java:788)
         at java.lang.System.loadLibrary(System.java:834)
         at com.mobilesoft.sms.mobilesoftinfo.SendSMS.<clinit>(SendSMS.java:14)
         at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
Exception in thread "main"
通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。(建议使用Jbuilder进行编译,操作比较简单!)这个头文件就是Java和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */
  4. #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
  5. #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10.  * Class:     com_mobilesoft_sms_mobilesoftinfo_SendSMS
  11.  * Method:    SmsInit
  12.  * Signature: ()I
  13.  */
  14. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit
  15.   (JNIEnv *, jclass);
  16. /*
  17.  * Class:     com_mobilesoft_sms_mobilesoftinfo_SendSMS
  18.  * Method:    SmsSend
  19.  * Signature: ([B[B)I
  20.  */
  21. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend
  22.   (JNIEnv *, jclass, jbyteArray, jbyteArray);
  23. #ifdef __cplusplus
  24. }
  25. #endif
  26. #endif

对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。
  1. /*
  2.  * SMS API 
  3.  * Author: yippit
  4.  * Date: 2004.6.8
  5.  */
  6. #ifndef MCS_SMS_H
  7. #define MCS_SMS_H
  8. #define DLLEXPORT __declspec(dllexport)
  9. /*sms storage*/
  10. #define SMS_SIM        0
  11. #define SMS_MT        1
  12. /*sms states*/
  13. #define SMS_UNREAD        0
  14. #define SMS_READ            1
  15. /*sms type*/
  16. #define SMS_NOPARSE        -1
  17. #define SMS_NORMAL        0
  18. #define SMS_FLASH            1
  19. #define SMS_MMSNOTI        2
  20. typedef struct tagSmsEntry {
  21.     int index;        /*index, start from 1*/
  22.     int status;        /*read, unread*/
  23.     int type;            /*-1-can't parser 0-normal, 1-flash, 2-mms*/
  24.     int storage;    /*SMS_SIM, SMS_MT*/
  25.     char date[24];
  26.     char number[32];
  27.     char text[144];
  28. } SmsEntry;
  29. DLLEXPORT int SmsInit(void);
  30. DLLEXPORT int SmsSend(char *phonenum, char *content);
  31. DLLEXPORT int SmsSetSCA(char *sca);
  32. DLLEXPORT int SmsGetSCA(char *sca);
  33. DLLEXPORT int SmsSetInd(int ind);
  34. DLLEXPORT int SmsGetInd(void);
  35. DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);
  36. DLLEXPORT int SmsSaveFlash(int flag);
  37. DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index); 
  38. DLLEXPORT int SmsDelete(int storage, int index);
  39. DLLEXPORT int SmsModifyStatus(int storage, int index); /*unread -> read*/
  40. #endif

在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比较简单,(大多是打印字符串等)所以避开了JNI中最麻烦的部分,也是最关键的部分,参数的传递。由于Java和C的编码是不同的,所以传递的参数是要进行再处理,否则C程序是会对参数在编译过程中提出警告,例如;warning C4024: 'SmsSend' : different types for formal and actual parameter 2等。
Sms.c的程序如下:
  1. #include "sms.h"
  2. #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"
  3. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)
  4. {
  5.     return SmsInit();
  6. }
  7. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)
  8. {
  9.     char * pSmscontent ;
  10.     //jsize theArrayLengthJ = (*env)->GetArrayLength(env,mobileno);
  11.     jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);
  12.     char * pMobileNo = (char *)arrayBody;
  13.     printf("[%s]\n ", pMobileNo);
  14.     //jsize size = (*env)->GetArrayLength(env,smscontent);
  15.     arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);
  16.     pSmscontent = (char *)arrayBody;
  17.     printf("<%s>\n", pSmscontent);
  18.     return SmsSend(pMobileNo,pSmscontent); 
  19. }

对于C或C++,在程序上是会有稍微的不同,这可以由读者对其进行适当的修改。这里要注意的是GetArrayLength,GetByteArrayElements等这些JNI中已经包含的方法,这些方法是专门对转换参数类型而提供的。具体的方法有很多,在下一篇中会做专门的介绍。
在完成了上述的文件后,可以对sms.c进行编译,生成.dll文件(建议在release中编译,这样动态链接库的容积会比较小!)
完成.dll文件的编译后,就可以在Java中调用C程序中的方法了。例如文件test.java
  1. public class test {
  2.   public test() {
  3.   }
  4.   public static void main(String[] args) {
  5.     byte[] mobileno = {
  6.         0x31, 0x33, 0x36, 0x36, 0x31, 0x36, 0x33, 0x30, 0x36, 0x36, 0x37, 0x00};
  7.     String smscontentemp = "早上好";
  8.     byte[] temp = {0};
  9.    try {
  10.       byte[] smscontentdb = smscontentemp.getBytes("gbk");
  11.       byte[] smscontent = new byte[smscontentdb.length + temp.length];
  12.       System.arraycopy(smscontentdb, 0, smscontent, 0, smscontentdb.length);
  13.       System.arraycopy(temp, 0, smscontent, smscontentdb.length, temp.length);
  14.       SendSMS sendSMS = new SendSMS();
  15.       sendSMS.SmsInit();
  16.       if (sendSMS.SmsSend(mobileno, smscontent) >= 0) {
  17.         System.out.println("chenggong !");
  18.       }
  19.       else {
  20.         System.out.println("shibai !");
  21.       }
  22.     }catch (Exception ex) {}
  23.   }
  24. }

在这个文件中要注意的有一点,就是在传递字节数组到C程序中时,最后的结尾一定要以0结束。这是一个偷懒的做法,不过是个有效的做法。因为大多数情况下,接口是由第三方提供的。所以我们一般是不知道在C的方法里,具体是怎么处理参数的。而C又是要求数组是有长度。所以,在Java中,如果你不想写程序传数组的长度,那么在数组中以0结尾就是最方便的方法了。当然,如果有更好的方法也希望大家提出。
到这里,一个完整的Java通过JNI调用动态链接库的程序就完成了。实际上也不是很复杂。只要多注意一下细节,是很容易得出来的。
posted @ 2005-08-10 10:30 kyanite 阅读(2800) | 评论 (0)编辑 收藏