2010年4月2日
http://www.oracle.com/technetwork/java/archive-139210.html
javap -g Foo.java (-g 生成行号)
javap -c -s -l -verbose Foo
装箱:从基本类型转换成Object类型,称之为装箱;***拆箱:从Object转换乘基本类型的操作,称之为拆箱。 这个操作在反射过程中用的比较的多。 装箱:在堆中建立一个Object实例,把你指定的值复制成去;***拆箱:判别引用指向的堆中信息是否是要拆成的类型,是取出堆中值送给栈中变量,否则报异常
//在-128~127 之外的数 Integer num1 = 297; Integer num2 = 297; System.out.println("num1==num2: "+(num1==num2)); // 在-128~127 之内的数 Integer num3 = 97; Integer num4 = 97; System.out.println("num3==num4: "+(num3==num4)); 打印的结果是:num1==num2: false num3==num4: true
很奇怪吧:这就归结于java对于Integer与int的自动装箱与拆箱的设计,是一种模式:叫享元模式(flyweight) 为了加大对简单数字的重利用,java定义:在自动装箱时对于值从–128到127之间的值,它们被装箱为Integer对象后,会存在内存中被重用,始终只存在一个对象 而如果超过了从–128到127之间的值,被装箱后的Integer对象并不会被重用,即相当于每次装箱时都新建一个 Integer对象;明白了吧 以上的现象是由于使用了自动装箱所引起的,如果你没有使用自动装箱,而是跟一般类一样,用new来进行实例化,就会每次new就都一个新的对象;
1. Java中的泛型是什么 ? 使用泛型的好处是什么?
这是在各种Java泛型面试中,一开场你就会被问到的问题中的一个,主要集中在初级和中级面试中。那些拥有Java1.4或更早版本的开发背景的人都知道,在集合中存储对象并在使用前进行类型转换是多么的不方便。泛型防止了那种情况的发生。它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
2. Java的泛型是如何工作的 ? 什么是类型擦除 ?
这是一道更好的泛型面试题。泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List<String>在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。根据你对这个泛型问题的回答情况,你会得到一些后续提问,比如为什么泛型是由类型擦除来实现的或者给你展示一些会导致编译器出错的错误泛型代码。请阅读我的Java中泛型是如何工作的来了解更多信息。
3. 什么是泛型中的限定通配符和非限定通配符 ?
这是另一个非常流行的Java泛型面试题。限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。更多信息请参阅我的文章泛型中限定通配符和非限定通配符之间的区别。
4. List<? extends T>和List <? super T>之间有什么区别 ?
这和上一个面试题有联系,有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符。这两个List的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。在本段出现的连接中可以找到更多信息。
5. 如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?
编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。泛型方法的例子请参阅Java集合类框架。最简单的情况下,一个泛型方法可能会像这样:
public V put(K key, V value) { return cache.put(key, value); }
6. Java中如何使用泛型编写带有参数的类?
这是上一道面试题的延伸。面试官可能会要求你用泛型编写一个类型安全的类,而不是编写一个泛型方法。关键仍然是使用泛型类型来代替原始类型,而且要使用JDK中采用的标准占位符。
7. 编写一段泛型程序来实现LRU缓存?
对于喜欢Java编程的人来说这相当于是一次练习。给你个提示,LinkedHashMap可以用来实现固定大小的LRU缓存,当LRU缓存已经满了的时候,它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法,该方法会被put()和putAll()调用来删除最老的键值对。当然,如果你已经编写了一个可运行的JUnit测试,你也可以随意编写你自己的实现代码。
8. 你可以把List<String>传递给一个接受List<Object>参数的方法吗?
对任何一个不太熟悉泛型的人来说,这个Java泛型题目看起来令人疑惑,因为乍看起来String是一种Object,所以List<String>应当可以用在需要List<Object>的地方,但是事实并非如此。真这样做的话会导致编译错误。如果你再深一步考虑,你会发现Java这样做是有意义的,因为List<Object>可以存储任何类型的对象包括String, Integer等等,而List<String>却只能用来存储Strings。
List<Object> objectList; List<String> stringList; objectList = stringList; //compilation error incompatible types
9. Array中可以用泛型吗?
这可能是Java泛型面试题中最简单的一个了,当然前提是你要知道Array事实上并不支持泛型,这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能。
10. 如何阻止Java中的类型未检查的警告?
如果你把泛型和原始类型混合起来使用,例如下列代码,Java 5的javac编译器会产生类型未检查的警告,例如
List<String> rawList = new ArrayList() 注意: Hello.java使用了未检查或称为不安全的操作;
这种警告可以使用@SuppressWarnings("unchecked")注解来屏蔽。
Java泛型面试题补充更新:
我手头又拿到了几个Java泛型面试题跟大家分享下,这几道题集中在泛型类型和原始类型的区别上,以及我们是否可以用Object来代替限定通配符的使用等等:
Java中List<Object>和原始类型List之间的区别?
原始类型和带参数类型<Object>之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查,通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。这道题的考察点在于对泛型中原始类型的正确理解。它们之间的第二点区别是,你可以把任何带参数的类型传递给原始类型List,但却不能把List<String>传递给接受List<Object>的方法,因为会产生变异错误。更多详细信息请参阅Java中的泛型是如何工作的。
Java中List<?>和List<Object>之间的区别是什么?
这道题跟上一道题看起来很像,实质上却完全不同。List<?> 是一个未知类型的List,而List<Object>其实是任意类型的List。你可以把List<String>, List<Integer>赋值给List<?>,却不能把List<String>赋值给List<Object>。
List<?> listOfAnyType; List<Object> listOfObject = new ArrayList<Object>(); List<String> listOfString = new ArrayList<String>(); List<Integer> listOfInteger = new ArrayList<Integer>(); listOfAnyType = listOfString; //legal listOfAnyType = listOfInteger; //legal listOfObjectType = (List<Object>) listOfString; //compiler error - in-convertible types
想了解更多关于通配符的信息请查看Java中的泛型通配符示例
List<String>和原始类型List之间的区别.
该题类似于“原始类型和带参数类型之间有什么区别”。带参数类型是类型安全的,而且其类型安全是由编译器保证的,但原始类型List却不是类型安全的。你不能把String之外的任何其它类型的Object存入String类型的List中,而你可以把任何类型的对象存入原始List中。使用泛型的带参数类型你不需要进行类型转换,但是对于原始类型,你则需要进行显式的类型转换。
List listOfRawTypes = new ArrayList(); listOfRawTypes.add("abc"); listOfRawTypes.add(123); //编译器允许这样 - 运行时却会出现异常 String item = (String) listOfRawTypes.get(0); //需要显式的类型转换 item = (String) listOfRawTypes.get(1); //抛ClassCastException,因为Integer不能被转换为String List<String> listOfString = new ArrayList(); listOfString.add("abcd"); listOfString.add(1234); //编译错误,比在运行时抛异常要好 item = listOfString.get(0); //不需要显式的类型转换 - 编译器自动转换
Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。
这带来了很多好处:
1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3,潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
泛型在使用中还有一些规则和限制:
1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
3、泛型的类型参数可以有多个。
4、泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上成为“有界类型”。
5、泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName(Java.lang.String);
重写方法的规则:
1、参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载。
2、返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。
3、访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
4、重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。例如:
父类的一个方法申明了一个检查异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常。
而重载的规则:
1、必须具有不同的参数列表;
2、可以有不责骂的返回类型,只要参数列表不同就可以了;
3、可以有不同的访问修饰符;
4、可以抛出不同的异常;
重写与重载的区别在于:
重写多态性起作用,对调用被重载过的方法可以大大减少代码的输入量,同一个方法名只要往里面传递不同的参数就可以拥有不同的功能或返回值。
用好重写和重载可以设计一个结构清晰而简洁的类,可以说重写和重载在编写代码过程中的作用非同一般.
擦除法并不代表编译后的字节码中就不包含我们在源代码定义的泛型类型了。而是在字节码中引入新属性Signature 和 LocalVariableTypeTable 来存储泛型。这也是为什么可以通过返回值重载,及通过反射获取到泛型的根本原因
Collections.synchronizedMap是个比较老的API了,实际用起来还要手工做一些事。建议楼主用Java5的ConcurrentHashMap或Java6的ConcurrentSkipListMap
1、首先要清楚源码的编码方式是什么?如果源码的编码是utf-8 就需要这样修改Preferences General > Workspace 修改Text file encoding为UTF-8后才行。重新启动Eclipse就可以解决了()
向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了。
1.打开旺旺的聊天窗口
2.command+shift+n 会弹出登录匡
是因为左侧的视图是Project Explorer而不是Package Explorer,直接在菜单栏上面找到“Window”-“Show view...”-“Other”,搜索“package”找到Package Explorer,并且让它显示出来,就OK了,在Package Explorer里面是有Referenced Libraries的。
摘要: 什么是HTTP协议协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超文本传输协议(HTTP)是一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器 目前我们使用的是HTTP/1.1 版本Web服务器,浏览器,代理服务器当我们打开浏览器,在地址栏中输入URL,然后我们就看到了网页。 原理是怎样的呢?实际上我们输入URL后,我们的浏...
阅读全文
基于 HTTP 长连接的“服务器推”技术
Comet 简介
浏览器作为 Web 应用的前台,自身的处理功能比较有限。浏览器的发展需要客户端升级软件,同时由于客户端浏览器软件的多样性,在某种意义上,也影响了浏览器新技术的推广。在 Web 应用中,浏览器的主要工作是发送请求、解析服务器返回的信息以不同的风格显示。AJAX 是浏览器技术发展的成果,通过在浏览器端发送异步请求,提高了单用户操作的响应性。但 Web 本质上是一个多用户的系统,对任何用户来说,可以认为服务器是另外一个用户。现有 AJAX 技术的发展并不能解决在一个多用户的 Web 应用中,将更新的信息实时传送给客户端,从而用户可能在“过时”的信息下进行操作。而 AJAX 的应用又使后台数据更新更加频繁成为可能。
图 1. 传统的 Web 应用模型与基于 AJAX 的模型之比较 “服务器推”是一种很早就存在的技术,以前在实现上主要是通过客户端的套接口,或是服务器端的远程调用。因为浏览器技术的发展比较缓慢,没有为“服务器推”的实现提供很好的支持,在纯浏览器的应用中很难有一个完善的方案去实现“服务器推”并用于商业程序。最近几年,因为 AJAX 技术的普及,以及把 IFrame 嵌在“htmlfile“的 ActiveX 组件中可以解决 IE 的加载显示问题,一些受欢迎的应用如 meebo,gmail+gtalk 在实现中使用了这些新技术;同时“服务器推”在现实应用中确实存在很多需求。因为这些原因,基于纯浏览器的“服务器推”技术开始受到较多关注,Alex Russell(Dojo Toolkit 的项目 Lead)称这种基于 HTTP 长连接、无须在浏览器端安装插件的“服务器推”技术为“Comet”。目前已经出现了一些成熟的 Comet 应用以及各种开源框架;一些 Web 服务器如 Jetty 也在为支持大量并发的长连接进行了很多改进。关于 Comet 技术最新的发展状况请参考关于 Comet 的 wiki。
下面将介绍两种 Comet 应用的实现模型。
基于 AJAX 的长轮询(long-polling)方式
如 图 1 所示,AJAX 的出现使得 JavaScript 可以调用 XMLHttpRequest 对象发出 HTTP 请求,JavaScript 响应处理函数根据服务器返回的信息对 HTML 页面的显示进行更新。使用 AJAX 实现“服务器推”与传统的 AJAX 应用不同之处在于:
- 服务器端会阻塞请求直到有数据传递或超时才返回。
- 客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
- 当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端所有的信息取回。
图 2. 基于长轮询的服务器推模型 一些应用及示例如 “Meebo”, “Pushlet Chat” 都采用了这种长轮询的方式。相对于“轮询”(poll),这种长轮询方式也可以称为“拉”(pull)。因为这种方案基于 AJAX,具有以下一些优点:请求异步发出;无须安装插件;IE、Mozilla FireFox 都支持 AJAX。
在这种长轮询方式下,客户端是在 XMLHttpRequest 的 readystate 为 4(即数据传输结束)时调用回调函数,进行信息处理。当 readystate 为 4 时,数据传输结束,连接已经关闭。Mozilla Firefox 提供了对 Streaming AJAX 的支持, 即 readystate 为 3 时(数据仍在传输中),客户端可以读取数据,从而无须关闭连接,就能读取处理服务器端返回的信息。IE 在 readystate 为 3 时,不能读取服务器返回的数据,目前 IE 不支持基于 Streaming AJAX。
基于 Iframe 及 htmlfile 的流(streaming)方式
iframe 是很早就存在的一种 HTML 标记, 通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。
图 3. 基于流方式的服务器推模型 上节提到的 AJAX 方案是在 JavaScript 里处理 XMLHttpRequest 从服务器取回的数据,然后 Javascript 可以很方便的去控制 HTML 页面的显示。同样的思路用在 iframe 方案的客户端,iframe 服务器端并不返回直接显示在页面的数据,而是返回对客户端 Javascript 函数的调用,如“<script type="text/javascript">js_func(“data from server ”)</script>
”。服务器端将返回的数据作为客户端 JavaScript 函数的参数传递;客户端浏览器的 Javascript 引擎在收到服务器返回的 JavaScript 调用时就会去执行代码。
从 图 3 可以看到,每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。
使用 iframe 请求一个长连接有一个很明显的不足之处:IE、Morzilla Firefox 下端的进度栏都会显示加载没有完成,而且 IE 上方的图标会不停的转动,表示加载正在进行。Google 的天才们使用一个称为“htmlfile”的 ActiveX 解决了在 IE 中的加载显示问题,并将这种方法用到了 gmail+gtalk 产品中。Alex Russell 在 “What else is burried down in the depth's of Google's amazing JavaScript?”文章中介绍了这种方法。Zeitoun 网站提供的 comet-iframe.tar.gz,封装了一个基于 iframe 和 htmlfile 的 JavaScript comet 对象,支持 IE、Mozilla Firefox 浏览器,可以作为参考。(请参见 参考资源)
回页首
使用 Comet 模型开发自己的应用
上面介绍了两种基于 HTTP 长连接的“服务器推”架构,更多描述了客户端处理长连接的技术。对于一个实际的应用而言,系统的稳定性和性能是非常重要的。将 HTTP 长连接用于实际应用,很多细节需要考虑。
不要在同一客户端同时使用超过两个的 HTTP 长连接
我们使用 IE 下载文件时会有这样的体验,从同一个 Web 服务器下载文件,最多只能有两个文件同时被下载。第三个文件的下载会被阻塞,直到前面下载的文件下载完毕。这是因为 HTTP 1.1 规范中规定,客户端不应该与服务器端建立超过两个的 HTTP 连接, 新的连接会被阻塞。而 IE 在实现中严格遵守了这种规定。
HTTP 1.1 对两个长连接的限制,会对使用了长连接的 Web 应用带来如下现象:在客户端如果打开超过两个的 IE 窗口去访问同一个使用了长连接的 Web 服务器,第三个 IE 窗口的 HTTP 请求被前两个窗口的长连接阻塞。
所以在开发长连接的应用时, 必须注意在使用了多个 frame 的页面中,不要为每个 frame 的页面都建立一个 HTTP 长连接,这样会阻塞其它的 HTTP 请求,在设计上考虑让多个 frame 的更新共用一个长连接。
服务器端的性能和可扩展性
一般 Web 服务器会为每个连接创建一个线程,如果在大型的商业应用中使用 Comet,服务器端需要维护大量并发的长连接。在这种应用背景下,服务器端需要考虑负载均衡和集群技术;或是在服务器端为长连接作一些改进。
应用和技术的发展总是带来新的需求,从而推动新技术的发展。HTTP 1.1 与 1.0 规范有一个很大的不同:1.0 规范下服务器在处理完每个 Get/Post 请求后会关闭套接口连接; 而 1.1 规范下服务器会保持这个连接,在处理两个请求的间隔时间里,这个连接处于空闲状态。 Java 1.4 引入了支持异步 IO 的 java.nio 包。当连接处于空闲时,为这个连接分配的线程资源会返还到线程池,可以供新的连接使用;当原来处于空闲的连接的客户发出新的请求,会从线程池里分配一个线程资源处理这个请求。 这种技术在连接处于空闲的机率较高、并发连接数目很多的场景下对于降低服务器的资源负载非常有效。
但是 AJAX 的应用使请求的出现变得频繁,而 Comet 则会长时间占用一个连接,上述的服务器模型在新的应用背景下会变得非常低效,线程池里有限的线程数甚至可能会阻塞新的连接。Jetty 6 Web 服务器针对 AJAX、Comet 应用的特点进行了很多创新的改进,请参考文章“AJAX,Comet and Jetty”(请参见 参考资源)。
控制信息与数据信息使用不同的 HTTP 连接
使用长连接时,存在一个很常见的场景:客户端网页需要关闭,而服务器端还处在读取数据的堵塞状态,客户端需要及时通知服务器端关闭数据连接。服务器在收到关闭请求后首先要从读取数据的阻塞状态唤醒,然后释放为这个客户端分配的资源,再关闭连接。
所以在设计上,我们需要使客户端的控制请求和数据请求使用不同的 HTTP 连接,才能使控制请求不会被阻塞。
在实现上,如果是基于 iframe 流方式的长连接,客户端页面需要使用两个 iframe,一个是控制帧,用于往服务器端发送控制请求,控制请求能很快收到响应,不会被堵塞;一个是显示帧,用于往服务器端发送长连接请求。如果是基于 AJAX 的长轮询方式,客户端可以异步地发出一个 XMLHttpRequest 请求,通知服务器端关闭数据连接。
在客户和服务器之间保持“心跳”信息
在浏览器与服务器之间维持一个长连接会为通信带来一些不确定性:因为数据传输是随机的,客户端不知道何时服务器才有数据传送。服务器端需要确保当客户端不再工作时,释放为这个客户端分配的资源,防止内存泄漏。因此需要一种机制使双方知道大家都在正常运行。在实现上:
- 服务器端在阻塞读时会设置一个时限,超时后阻塞读调用会返回,同时发给客户端没有新数据到达的心跳信息。此时如果客户端已经关闭,服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。
- 如果客户端使用的是基于 AJAX 的长轮询方式;服务器端返回数据、关闭连接后,经过某个时限没有收到客户端的再次请求,会认为客户端不能正常工作,会释放为这个客户端分配、维护的资源。
- 当服务器处理信息出现异常情况,需要发送错误信息通知客户端,同时释放资源、关闭连接。
Pushlet - 开源 Comet 框架
Pushlet 是一个开源的 Comet 框架,在设计上有很多值得借鉴的地方,对于开发轻量级的 Comet 应用很有参考价值。
观察者模型
Pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。
客户端 JavaScript 库
pushlet 提供了基于 AJAX 的 JavaScript 库文件用于实现长轮询方式的“服务器推”;还提供了基于 iframe 的 JavaScript 库文件用于实现流方式的“服务器推”。
JavaScript 库做了很多封装工作:
- 定义客户端的通信状态:
STATE_ERROR
、STATE_ABORT
、STATE_NULL
、STATE_READY
、STATE_JOINED
、STATE_LISTENING
; - 保存服务器分配的会话 ID,在建立连接之后的每次请求中会附上会话 ID 表明身份;
- 提供了
join()
、leave()
、subscribe()
、 unsubsribe()
、listen()
等 API 供页面调用; - 提供了处理响应的 JavaScript 函数接口
onData()
、onEvent()
…
网页可以很方便地使用这两个 JavaScript 库文件封装的 API 与服务器进行通信。
客户端与服务器端通信信息格式
pushlet 定义了一套客户与服务器通信的信息格式,使用 XML 格式。定义了客户端发送请求的类型:join
、leave
、subscribe
、unsubscribe
、listen
、refresh
;以及响应的事件类型:data
、join_ack
、listen_ack
、refresh
、heartbeat
、error
、abort
、subscribe_ack
、unsubscribe_ack
。
服务器端事件队列管理
pushlet 在服务器端使用 Java Servlet 实现,其数据结构的设计框架仍可适用于 PHP、C 编写的后台客户端。
Pushlet 支持客户端自己选择使用流、拉(长轮询)、轮询方式。服务器端根据客户选择的方式在读取事件队列(fetchEvents)时进行不同的处理。“轮询”模式下 fetchEvents()
会马上返回。”流“和”拉“模式使用阻塞的方式读事件,如果超时,会发给客户端发送一个没有新信息收到的“heartbeat“事件,如果是“拉”模式,会把“heartbeat”与“refresh”事件一起传给客户端,通知客户端重新发出请求、建立连接。
客户服务器之间的会话管理
服务端在客户端发送 join
请求时,会为客户端分配一个会话 ID, 并传给客户端,然后客户端就通过此会话 ID 标明身份发出subscribe
和 listen
请求。服务器端会为每个会话维护一个订阅的主题集合、事件队列。
服务器端的事件源会把新产生的事件以多播的方式发送到每个会话(即订阅者)的事件队列里。
回页首
小结
本文介绍了如何在现有的技术基础上选择合适的方案开发一个“服务器推”的应用,最优的方案还是取决于应用需求的本身。相对于传统的 Web 应用, 目前开发 Comet 应用还是具有一定的挑战性。
“服务器推”存在广泛的应用需求,为了使 Comet 模型适用于大规模的商业应用,以及方便用户构建 Comet 应用,最近几年,无论是服务器还是浏览器都出现了很多新技术,同时也出现了很多开源的 Comet 框架、协议。需求推动技术的发展,相信 Comet 的应用会变得和 AJAX 一样普及。
vTCP连接的建立
v
第一次握手:客户端TCP首先给服务器端TCP发送一个特殊的TCP数据
段。该数据段不包含应用层数据,并将头部中的SYN位设置为1,所以该数
据段被称为SYN数据段。另外,客户选择一个初始序列号SEQ,设SEQ=x
并将这个编号放到初始的TCP SYN数据段的序列号字段中。该数据段被封
装到一个IP数据报中,并发送给服务器。
第二次握手:一旦装有TCP SYN数据段的IP数据报到达了服务器主机,服
务器将从该数据报中提取出TCP SYN数据段,给该连接分配TCP缓冲区和
变量,并给客户TCP发送一个允许连接的数据段。这个允许连接的数据段
也不包含任何应用层数据。但是,它的头部中装载着3个重要信息。首先,
SYN被设置为1;其次,TCP数据段头部的确认字段被设置为x+1;最后,
服务器选择自己的初始顺序号,SEQ=y,并将该值放到TCP数据段头部的
序列号字段中。
第三次握手:在接收到允许连接数据段之后,客户也会给连接分配缓冲区
和变量。客户端主机还会给服务器发送另一个数据段,对服务器的允许连
接数据段给出确认。
报错:springmvc threw exception com.ibatis.sqlmap.client.SqlMapException: There is
no statement named 语句名 in this SqlMap.sqlmap-config.xml 中 必须加上这行:<settings cacheModelsEnabled="true" enhancementEnabled="false" lazyLoadingEnabled="false" maxRequests="3000" maxSessions="3000" maxTransactions="3000" useStatementNamespaces="true"/>
让root用户可以远程登录
--------------------------------------------------------------------------------
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'mypassword' WITH GRANT OPTION;
以前发现在Ubuntu重启后,hosts
文件又恢复到了修改前,十分奇怪。
一开始觉得是Linux的问题,最近在Mac上同样的现象又出现了。
查看 /etc/目录下,发现了两个一摸一样的文件,hosts
和hosts.ac
,vimdiff一下,居然一模一样!
看来原因找到了。
Cisco AnyConnect 捣的鬼
仔细回想了一下,发现/ect/hosts.ac是出现在 VPN 客户端:Cisco AnyConnect
后,hosts.ac应该是any client的缩写。
这货每次在重启后,都会把/etc/hosts重新覆盖一遍。
所以,除非你同时修改了/etc/hosts.ac 文件,否则单独只修改/etc/hosts都会被重置。
下面开始实验证明一下
首先测试下做个软链是否有效:
删除原来hosts.ac sudo rm /etc/hosts.ac 建立软链 sudo ln -s /etc/hosts.ac /etc/hosts
重启后发现,两个hosts文件都不在了。。。悲剧 。
尝试反着操作
删除原来hosts sudo rm /etc/hosts 建立软链 sudo ln -s /etc/hosts /etc/hosts.ac
再次重启,发现软连接消失了,依旧变成了连个一模一样的hosts.ac 。
实验证明
每次重启,hosts.ac都会重新复制给hosts,
所以如果你希望hosts保留的话,每次修改hosts后,请同时复制给hosts.ac文件
如果不小心被误删除了,可以使用原始的hosts文件内容恢复:
255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost
偷懒的解决方案
在BASH的PATH目录下,创建mh
脚本,以后通过这个脚本修改hosts文件
#!/bin/bash #!/bin/bash
modify hosts
if [ -f /etc/hosts ];then echo "/etc/hosts exists,back up to ~/hosts.bak" cp /etc/hosts ~/hosts.bak sudo rm /etc/hosts fi if [ ! -L /etc/hosts ];then echo "link /etc/hosts.ac => /etc/hosts" sudo ln -s /etc/hosts.ac /etc/hosts fi sudo vi /etc/hosts.ac
出错如下:
New emulator found: emulator-5554
Waiting for HOME ('android.process.acore') to be launched...
解决方法:保持AVD,不要关闭,重新运行工程。
在网络编程领域(注意不是Web编程),几乎每天都与socket打交道。然而不知如何,国人竟把socket翻译成了“套接字”。socket的原意是指电源插座,而“套接字”为何物?
虽然这种翻译已经被绝大多数国人认可,可这也恰恰给网络编程的入门及其本质的理解造成了难以理解的巨大恶果。还是先让我们来看看插座吧。
对于电源插座,相信大家都不会陌生了。看看下面这个再简单不过的生活经历:
小王装修房子,第一天,为了用电,必须找一根电缆从变电站把电引入家中。也就是家里用电的总来源要有啊。
第二天,小王买来了电冰箱,为了给电冰箱供电,必须先买一个插座,并把插座接到第一天引入的电源线上。剩下来就简单了,因为有了插座(socket),把冰箱的电源插头插入就OK了。
第三天,热爱计算机的小王买来了心爱的电脑,可这下就犯愁了,怎么供电呢?好办,又买了一个插座(socket)并接到电源线上,把电脑的插头插入socket,电脑终于可以正常使用了。
第四天,小王又买来了电视机,同样一个socket,又让电视机顺利地工作了。
第五天,第六天...按照同样的方法,小王通过一个又一个的插座(socket),完成了所有电器的供电。
看完了这个例子,再想想网络编程,简直是如出一辙。
为了连上网络,必须要有网卡(总电源),这样网络上的数据便可以到达你的计算机了。可是你的应用程序如何取得这些网络数据呢,很简单,建立一个socket,并连到网卡上,好了这下你就可以从插座里取得信息了。无论何时你想取得网络数据只需连接一个socket即可。
看看,socket这个词,用到网络编程API上是何等的形象!足见创始人对网络编程模型的准确把握和良苦用心。然而我们的翻译者却把这么好的一个术语弄成了“套接字”,本来很简单直观的网络编程被蒙上了一层复杂深奥的迷雾。
注意:当然网络编程中的Socket与电源插座有一定的差别,网络中的socket种类和功能更多更强,但是其本质是一样的。按照插座来理解网络编程,将非常的自然清晰。
test
1.下载Array Networks提供的客户端程序 array_vpnc.bin
sudo apt-get install libc6-i386 #64位系统也是这个包
wget http://q.pnq.cc/uploads/array_vpnc.bin
chmod a+x array_vpnc.bin
2.下载这个小脚本到同个目录
#下载辅助脚本
wget http://q.pnq.cc/uploads/vpn-for-common.sh -O vpn.sh
#里面会包含重要信息,我们不想别人随便访问
chmod 700 vpn.sh
3.修改vpn.sh中的配置,将vpn_host、user、key修改为你的配置
vpn_host=your_vpn_server
user=your_user_name
key=your_static_passwd #密� �中不变的部分
使用方法:
./vpn.sh
然后根据提示输入,当看到这个提示时,就说明成功了:
array_vpnc: VPN TUNNEL SUCCESSFUL!
public class PerformanceTest {
public static void main(String[] args){
long time1 = System.currentTimeMillis();
Long sum=0L;
for(long i=0;i<Integer.MAX_VALUE;i++){
sum+=i;
}
System.out.println(sum);
long time2 = System.currentTimeMillis();
System.out.println((time2 - time1)/1000 + "秒。");
}
}
上面的代码执行了28秒
public class PerformanceTest {
public static void main(String[] args){
long time1 = System.currentTimeMillis();
long sum=0L;
for(long i=0;i<Integer.MAX_VALUE;i++){
sum+=i;
}
System.out.println(sum);
long time2 = System.currentTimeMillis();
System.out.println((time2 - time1)/1000 + "秒。");
}
}
上面的代码执行了7秒
第一段代码构造了大约2的31次放的Long 实例
如果你用的SVNKit, 找到以下目录并删除.keyring文件.
[eclipsehome ]/"configuration"/org.eclipse .core.runtime
<context-param>的作用:
web.xml的配置中<context-param>配置作用
1. 启动一个WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点: <listener></listener> 和 <context-param></context-param>
2.紧接着,容器创建一个ServletContext(上下文),这个WEB项目所有部分都将共享这个上下文.
3.容器将<context-param></context-param>转化为键值对,并交给ServletContext.
4.容器创建<listener></listener>中的类实例,即创建监听.
5.在监听中会有contextInitialized(ServletContextEvent args)初始化方法,在这个方法中获得ServletContext = ServletContextEvent.getServletContext();
context-param的值 = ServletContext.getInitParameter("context-param的键");
6.得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比所有的Servlet都要早.
换句话说,这个时候,你对<context-param>中的键值做的操作,将在你的WEB项目完全启动之前被执行.
7.举例.你可能想在项目启动之前就打开数据库.
那么这里就可以在<context-param>中设置数据库的连接方式,在监听类中初始化数据库的连接.
8.这个监听是自己写的一个类,除了初始化方法,它还有销毁方法.用于关闭应用前释放资源.比如说数据库连接的关闭.
如:
<!-- 加载spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml,/WEB-
INF/jason-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
又如: --->自定义context-param,且自定义listener来获取这些信息
<context-param>
<param-name>urlrewrite</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>cluster</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>servletmapping</param-name>
<param-value>*.bbscs</param-value>
</context-param>
<context-param>
<param-name>poststoragemode</param-name>
<param-value>1</param-value>
</context-param>
<listener>
<listener-class>com.laoer.bbscs.web.servlet.SysListener</listener-class>
</listener>
public class SysListener extends HttpServlet implements ServletContextListener {
private static final Log logger = LogFactory.getLog(SysListener.class);
public void contextDestroyed(ServletContextEvent sce) {
//用于在容器关闭时,操作
}
//用于在容器开启时,操作
public void contextInitialized(ServletContextEvent sce) {
String rootpath = sce.getServletContext().getRealPath("/");
System.out.println("-------------rootPath:"+rootpath);
if (rootpath != null) {
rootpath = rootpath.replaceAll("
\\\\", "/");
} else {
rootpath = "/";
}
if (!rootpath.endsWith("/")) {
rootpath = rootpath + "/";
}
Constant.ROOTPATH = rootpath;
logger.info("Application Run Path:" + rootpath);
String urlrewrtie =
sce.getServletContext().getInitParameter("urlrewrite"); boolean burlrewrtie = false;
if (urlrewrtie != null) {
burlrewrtie = Boolean.parseBoolean(urlrewrtie);
}
Constant.USE_URL_REWRITE = burlrewrtie;
logger.info("Use Urlrewrite:" + burlrewrtie);
其它略之....
}
}
/*最终输出
-------------rootPath:D:\tomcat_bbs\webapps\BBSCS_8_0_3\
2009-06-09 21:51:46,526 [com.laoer.bbscs.web.servlet.SysListener]-[INFO]
Application Run Path:D:/tomcat_bbs/webapps/BBSCS_8_0_3/
2009-06-09 21:51:46,526 [com.laoer.bbscs.web.servlet.SysListener]-[INFO]
Use Urlrewrite:true
2009-06-09 21:51:46,526 [com.laoer.bbscs.web.servlet.SysListener]-[INFO]
Use Cluster:false
2009-06-09 21:51:46,526 [com.laoer.bbscs.web.servlet.SysListener]-[INFO]
SERVLET MAPPING:*.bbscs
2009-06-09 21:51:46,573 [com.laoer.bbscs.web.servlet.SysListener]-[INFO]
Post Storage Mode:1
*/
context-param和init-param区别
web.xml里面可以定义两种参数:
(1)application范围内的参数,存放在servletcontext中,在web.xml中配置如下:
<context-param>
<param-name>context/param</param-name>
<param-value>avalible during application</param-value>
</context-param>
(2)servlet范围内的参数,只能在servlet的init()方法中取得,在web.xml中配置如下:
<servlet>
<servlet-name>MainServlet</servlet-name>
<servlet-class>com.wes.controller.MainServlet</servlet-class>
<init-param>
<param-name>param1</param-name>
<param-value>avalible in servlet init()</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
在servlet中可以通过代码分别取用:
package com.wes.controller;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
public class MainServlet extends HttpServlet ...{
public MainServlet() ...{
super();
}
public void init() throws ServletException ...{
System.out.println("下面的两个参数param1是在servlet中存放的");
System.out.println(this.getInitParameter("param1"));
System.out.println("下面的参数是存放在servletcontext中的");
System.out.println(getServletContext().getInitParameter("context/param"));
}
}
第一种参数在servlet里面可以通过getServletContext().getInitParameter("context/param")得到
第二种参数只能在servlet的init()方法中通过this.getInitParameter("param1")取得.
$ sudo apt-get install linux-headers-server linux-image-server linux-server
安装前后看free -m 安装后需要重启。
命令:
dpkg -i package_file.deb
dpkg -r package_name
注意:使用此命令需要你自己注意依赖软件,所以这并不是安装软件的最佳方法.
dpkg的详细使用方法,网上有很多,下面简单列了几个:
dpkg -i package.deb | 安装包 |
dpkg -r package | 删除包 |
dpkg -P package | 删除包(包括配置文件) |
dpkg -L package | 列出与该包关联的文件 |
dpkg -l package | 显示该包的版本 |
dpkg –unpack package.deb | 解开 deb 包的内容 |
dpkg -S keyword | 搜索所属的包内容 |
dpkg -l | 列出当前已安装的包 |
dpkg -c package.deb | 列出 deb 包的内容 |
dpkg –configure package | 配置包 |
mvn dependency:tree > dep.txt
http://www.oixx.se/fastfox.php
PL/SQL Developer中文乱码,但通过secureCRT连接Oracle显示正常,需要通过以下步骤进行修改。
1、查看服务端的字符集:
select * from v$nls_parameters;
2、修改注册表
HKEY_LOCAL_MACHINE/SOFTWARE/ORACLE/HOMEO/NLS_LANG
该项值改为和数据库服务端一致
注:如果不放心,修改该键值:HKEY_LOCAL_MACHINE/SOFTWARE/ORACLE/ALL_HOMES/ID0/NLS_LANG
3、修改环境变量
在系统环境变量中设置NLS_LANG,和服务端一致
Class 和 Class 之间是 extends
Interface 和 Interface 之间是 extends
Class 和 Interface 之间是 implements
Interface 和 abstract Interface 之间仅仅只能被你自己定义的接口extends,不可能implements, implements是Class和Interface之间的 "专利 "
一句话 就是 abstract interface 只能被interface继承,不能直接被类实现
1、mvn archetype:create -DgroupId=org.david.app -DartifactId=mywebapp -DarchetypeArtifactId=maven-archetype-webapp
2、cd mywebapp
mvn eclipse:eclipse
导入eclipse工程
(或者直接从eclipse中导入maven工程)
3、添加servlet依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
4、添加源代码目录src/main/java
将源代码放在该目录下。
5、添加jetty插件
<build>
<finalName>mywebapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.10</version>
</plugin>
</plugins>
</build>
6、命令行:mvn jetty:run
前些日子安装了Ubuntu 10.04
,体验很爽,不过就是默认的输入法不好使,感觉比9.10
是个倒退,fcitx
也没有google
或者搜狗拼音那么爽,幸运的是SCIM-GooglePinyin
项目试图将 Android
上的 Google
拼音输入法移植到 GNU/Linux
平台。SCIM-GooglePinyin
基于SCIM
这个通用输入法平台,目前仍然在开发之中,不过其源代码已经可以通过 github 获取。
安装步骤(需要自己编译):
A. 获取代码:(没有git的先安装git:sudo apt-get install git-core)
$ git clone git://github.com/tchaikov/scim-googlepinyin.git
$ cd scim-googlepinyin.git
B. 编译前提:
上面给的链接里面有介绍怎么编译的,但少提了几个必需组件,这里列一下:
* autotools-dev
* libgtk2.0-dev
* libscim-dev
* libtool
* automake
用下面命令看看是不是安装了,如果没有,会自动帮你安装上:
$ aptitude install autotools-dev libgtk2.0-dev libscim-dev libtool automake
C. 编译:
记住系统必须先存在SCIM(没有的话 sudo apt-get install scim 一下)
$ ./autogen.sh
$ make
$ sudo make install
现在重启scim:
关闭scim:pkill scim
然后启动: scim -d (本人测试要重新启动机器)
OK,去首选项里的“语言支持”的“输入法”选择scim作为默认输入法就OK了。
您可以遵循以下步骤,使您的 gedit 正确显示中文编码文件。
- 按下 Alt-F2,打开“运行应用程序”对话框。
- 在文本框中键入“gconf-editor”,并按下回车键,打开“配置编辑器”。
- 展开左边的树节点,找到 /apps/gedit-2/preferences/encodings 节点并单击它。
- 双击右边的 auto_detected 键,打开“编辑键”对话框。
- 单击列表右边的“添加”按钮,输入“GB18030”,单击确定按钮。
- 列表的最底部新增加了一个“GB18030”。单击选中它,并单击右边的 “向上” 按钮直到 “GB18030”
位于列表的顶部为止。
- 单击确定按钮,关闭配置编辑器。
安装完重启后会发现无法进入Windows 7,需要我们进行如下步骤:
ubuntu9.10使用grub2,所以不能再使用menu.lst而使用grub.cfg来代替
修改grub需要root权限,默认是只读的,所以不建议直接修改
进入Ubuntu,打开终端,输入“sudo update-grub",即可重新生成grub.cfg,并且会自动搜索并添加剂windows系统的启动项目
如果还是没有windows7的启动项,也可以修改/etc/grub.d/目录下的40_custom,在后面添加
1
title Windows Seven
2
root (hd0,0)
3
makeactive
4
chainloader +1
然后再执行"sudo update-grub"
1、建用户:
adduser box //新建box用户
passwd box //给box用户设置密码
2、建工作组
groupadd test //新建test工作组
3、新建用户同时增加工作组
useradd -g test box //新建box用户并增加到test工作组
注::-g 所属组 -d 家目录 -s 所用的SHELL
4、给已有的用户增加工作组
usermod -G groupname username
或者:gpasswd -a user group
5、临时关闭:在/etc/shadow文件中属于该用户的行的第二个字段(密码)前面加上*就可以了。想恢复该用户,去掉*即可。
或者使用如下命令关闭用户账号:
passwd peter –l
重新释放:
passwd peter –u
6、永久性删除用户账号
userdel peter
groupdel peter
usermod –G peter peter (强制删除该用户的主目录和主目录下的所有文件和子目录)
7、从组中删除用户
编辑/etc/group 找到GROUP1那一行,删除 A
或者用命令
gpasswd -d A GROUP
8、显示用户信息
id user
cat /etc/passwd
/etc/group 内容具体分析
/etc/group 的内容包括用户组(Group)、用户组口令、GID及该用户组所包含的用户(User),每个用户组一条记录;格式如下:
group_name:passwd:GID:user_list
在/etc/group 中的每条记录分四个字段:
第一字段:用户组名称;
第二字段:用户组密码;
第三字段:GID
第四字段:用户列表,每个用户之间用,号分割;本字段可以为空;如果字段为空表示用户组为GID的用户名;
我们举个例子:
root:x:0:root,linuxsir 注:用户组root,x是密码段,表示没有设置密码,GID是0,root用户组下包括root、linuxsir以及GID为0的其它用户(可以通过/etc/passwd查看);;
beinan:x:500:linuxsir 注:用户组beinan,x是密码段,表示没有设置密码,GID是500,beinan用户组下包括linuxsir用户及GID为500的用户(可以通过/etc/passwd查看);
linuxsir:x:502:linuxsir 注:用户组linuxsir,x是密码段,表示没有设置密码,GID是502,linuxsir用户组下包用户linuxsir及GID为502的用户(可以通过/etc/passwd查看);
helloer:x:503: 注:用户组helloer,x是密码段,表示没有设置密码,GID是503,helloer用户组下包括GID为503的用户,可以通过/etc/passwd查看;