2009年11月23日
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查看;
sudo gedit /etc/NetworkManager/nm-system-settings.conf
把
managed=false
修改成为
managed=true
1.从trunk上创建一个分支A svn cp trunk A -m "描述"例如 svn cp http://svn/trunk/ http://svn/branches/lijun_log/ -m"out log"
2.查看要合并分支的log
svn log --stop-on-copy -q http://svn/B
3.svn merge -r B分支开始的版本:B分支结束的版本 A分支的ulr 列如: svn merge -r 17514:18999 /path
这样主干就合并到B分支上
摘要: JDK源码分析Set类,因为Set类是经常要用到的,那我们知道JDK源码中Set类在其中不可以有相同的元素,那么判断这个元素是否相同是如何实现的呢,我们看下下面这张图:
对JDK源码分析之Set类在这张类图上,首先我们看见一个经典模式的应用,那就是适配器模式,我们把map接口的对象,包装成为了Set的接口;在代码中,我们来分析一下;
首先,我...
阅读全文
标识符类型 命名规则 例子
包
(Packages) 一个唯一包名的前缀总是全部小写的ASCII字母并且是一个顶级域名,通常是com,edu,gov,mil,net,org,或1981年ISO 3166标准所指定的标识国家的英文双字符代码。包名的后续部分根据不同机构各自内部的命名规范而不尽相同。这类命名规范可能以特定目录名的组成来区分部门(department),项目(project),机器(machine),或注册名(login names)。 com.sun.eng
com.apple.quicktime.v2
edu.cmu.cs.bovik.cheese
类
(Classes) 命名规则:类名是个一名词,采用大小写混合的方式,每个单词的首字母大写。尽量使你的类名简洁而富于描述。使用完整单词,避免缩写词(除非该缩写词被更广泛使用,像URL,HTML) class Raster;
class ImageSprite;
接口(Interfaces) 命名规则:大小写规则与类名相似 interface RasterDelegate;
interface Storing;
方法
(Methods) 方法名是一个动词,采用大小写混合的方式,第一个单词的首字母小写,其后单词的首字母大写。 run();
runFast();
getBackground();
变量(Variables) 除了变量名外,所有实例,包括类,类常量,均采用大小写混合的方式,第一个单词的首字母小写,其后单词的首字母大写。变量名不应以下划线或美元符号开头,尽管这在语法上是允许的。
变量名应简短且富于描述。变量名的选用应该易于记忆,即,能够指出其用途。尽量避免单个字符的变量名,除非是一次性的临时变量。临时变量通常被取名为i,j,k,m和n,它们一般用于整型;c,d,e,它们一般用于字符型。 char c;
int i;
float myWidth;
实例变量(Instance Variables) 大小写规则和变量名相似,除了前面需要一个下划线 int _employeeId;
String _name;
Customer _customer;
常量(Constants) 类常量和ANSI常量的声明,应该全部大写,单词间用下划线隔开。(尽量避免ANSI常量,容易引起错误) static final int MIN_WIDTH = 4;
static final int MAX_WIDTH = 999;
static final int GET_THE_CPU = 1;
@关于间接层的概念。
间接层就是我们所提炼出来的小函数。本来事情是可以交给一个大函数一次性去执行完的,可是我们为什么还要把他分割成小函数,再委托小函数这个间接层去完成事情呢。以下是作者总结出来的三点间接层的好处。
1、 允许逻辑共享。也就是说小函数做的这些小事情,子类也同样可以做,而且跟其他事情互不干扰。
2、间接层给了我们一个解释自己意图的机会。间接层允许我们选择最适合表达我们意图的名字来命名,那在调用这些函数的时候,我一看他们的名字就知道他们可能会做些什么事情。就像Justin昨天写的那个 A()
{
…
….
…. 200 行
}
改成
A()
{
A1();
A2();
A3();
}
A1()
{
…….
}
A2(){….}
A3(){….}
这样你在整理逻辑的时候,头脑会很清醒。(因为名字是我们赋予他们的意义)
3、将变化加以隔离,当我需要修改一些逻辑的时候,我可以把我在整个项目中掀起的波澜降低很多。
@在有些开源的框架内,我的接口名字已经公布了,可是现在我在重构的时候需要改变这些接口名字,改怎么办呢?
这时候可以保留旧的接口,然后在旧的接口中用新接口来实现逻辑。一直持续到所有用户都开始使用新接口的时候,再把这个旧接口去掉。
@这个世界不存在一条万能的定律能解决一切问题(我曾经想象要是有这么一条定律就好了,这也是很多哲学家,科学家所追求过的),所以不要试图用重构来拯救整个世界。有以下几种情况重构不适用。
当代码已经腐朽到连正常功能都不能运行的时候,也许重写(从头再来一遍)比重构要来的简单。
有的时候临近发布,我们要赶眼前的时间,就不应重构了。重构其实是一剂中药,虽然药效很好,但是效果却也来的慢一些。如果是赶时间发布,那就不要寄希望于重构了。重构是我们欠的债,很多时候我们都是举债来发布的,但是债都是有利息的,“过于复杂的代码所造成的【维护和扩展的额外开销】就是利息。一定的利息我们可以承受,但是利息过高就会被拖垮。“出来混总是要还的”。最近几次发布都通宵,这也许就是我们的重构债欠的有点过多的信号。是时候来偿还一些债了。
在此描述您的新便笺。软件中总免不了要使用诸如 Log4net, Log4j, Tracer 等东东来写日志,不管用什么,这些东东大多是大同小异的,一般都提供了这样5个日志级别:
× Debug
× Info
× Warn
× Error
× Fatal
一个等级比一个高,但是在具体开发中,关于应该如何选择适应的等级,却没有找到好的文章进行说明。记录一下自己的一些看法,以便日后使用吧。
=== Debug ===
这个级别最低的东东,一般的来说,在系统实际运行过程中,一般都是不输出的。
因此这个级别的信息,可以随意的使用,任何觉得有利于在调试时更详细的了解系统运行状态的东东,比如变量的值等等,都输出来看看也无妨。
当然,在每一个 Debug 调用之前,一定要加上 If 判断。
=== Info ===
这个应该用来反馈系统的当前状态给最终用户的,所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。
从某种角度上说,Info 输出的信息可以看作是软件产品的一部分(就像那些交互界面上的文字一样),所以需要谨慎对待,不可随便。
=== Warn、Error、Fatal ===
警告、错误、严重错误,这三者应该都在系统运行时检测到了一个不正常的状态,他们之间的区别,要区分还真不是那么简单的事情。我大致是这样区分的:
所谓警告,应该是这个时候进行一些修复性的工作,应该还可以把系统恢复到正常状态中来,系统应该可以继续运行下去。
所谓错误,就是说可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段,很可能会因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止也不出现严重问题。
所谓Fatal,那就是相当严重的了,可以肯定这种错误已经无法修复,并且如果系统继续运行下去的话,可以肯定必然会越来越乱。这时候采取的最好的措施不是试图将系统状态恢复到正常,而是尽可能地保留系统有效数据并停止运行。
也就是说,选择 Warn、Error、Fatal 中的具体哪一个,是根据当前的这个问题对以后可能产生的影响而定的,如果对以后基本没什么影响,则警告之,如果肯定是以后要出严重问题的了,则Fatal之,拿不准会怎么样,则 Error 之。
=== 一些疑惑 ===
不过在实际使用中,基于上面的这种考虑,也还是有一些具体问题。最常见的就是要在最终产品中将输出日志打开到那种级别才算好呢?
例如在应用中有一个输出窗口,一些系统状态信息将被输出到这个输出窗口中。因为 Info 的级别是如此之低,所以为了让用户能够看到有效的输出信息,必须将日志级别开放到 Info 级别。但是 Warn 的级别比 Info 要高,所以用户不得不被迫看到一些 Warn 的信息。而我们其实已经假定,Warn 信息其实并不影响系统的正常运行,这一般只代表系统中存在一些还没有被发现或者修改的小 Bug。这些 Warn 信息会让最终用户困惑甚至恐慌,系统发出警告了,该怎么办?
个人观点,Info 的级别应该比 Warn 更高才对,Warn 信息和 Debug 一样,应该在产品测试和调试时使用,而 Info、Erro 以及 Fatal 则在产品发布后需要继续使用。
软件中总免不了要使用诸如 Log4net, Log4j, Tracer 等东东来写日志,不管用什么,这些东东大多是大同小异的,一般都提供了这样5个日志级别:
× Debug
× Info
× Warn
× Error
× Fatal
一个等级比一个高,但是在具体开发中,关于应该如何选择适应的等级,却没有找到好的文章进行说明。记录一下自己的一些看法,以便日后使用吧。
=== Debug ===
这个级别最低的东东,一般的来说,在系统实际运行过程中,一般都是不输出的。
因此这个级别的信息,可以随意的使用,任何觉得有利于在调试时更详细的了解系统运行状态的东东,比如变量的值等等,都输出来看看也无妨。
当然,在每一个 Debug 调用之前,一定要加上 If 判断。
=== Info ===
这个应该用来反馈系统的当前状态给最终用户的,所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。
从某种角度上说,Info 输出的信息可以看作是软件产品的一部分(就像那些交互界面上的文字一样),所以需要谨慎对待,不可随便。
软件中总免不了要使用诸如 Log4net, Log4j, Tracer 等东东来写日志,不管用什么,这些东东大多是大同小异的,一般都提供了这样5个日志级别:
× Debug
× Info
× Warn
× Error
× Fatal
一个等级比一个高,但是在具体开发中,关于应该如何选择适应的等级,却没有找到好的文章进行说明。记录一下自己的一些看法,以便日后使用吧。
=== Debug ===
这个级别最低的东东,一般的来说,在系统实际运行过程中,一般都是不输出的。
因此这个级别的信息,可以随意的使用,任何觉得有利于在调试时更详细的了解系统运行状态的东东,比如变量的值等等,都输出来看看也无妨。
当然,在每一个 Debug 调用之前,一定要加上 If 判断。
=== Info ===
这个应该用来反馈系统的当前状态给最终用户的,所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。
从某种角度上说,Info 输出的信息可以看作是软件产品的一部分(就像那些交互界面上的文字一样),所以需要谨慎对待,不可随便。
=== Warn、Error、Fatal ===
警告、错误、严重错误,这三者应该都在系统运行时检测到了一个不正常的状态,他们之间的区别,要区分还真不是那么简单的事情。我大致是这样区分的:
所谓警告,应该是这个时候进行一些修复性的工作,应该还可以把系统恢复到正常状态中来,系统应该可以继续运行下去。
所谓错误,就是说可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段,很可能会因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止也不出现严重问题。
所谓Fatal,那就是相当严重的了,可以肯定这种错误已经无法修复,并且如果系统继续运行下去的话,可以肯定必然会越来越乱。这时候采取的最好的措施不是试图将系统状态恢复到正常,而是尽可能地保留系统有效数据并停止运行。
也就是说,选择 Warn、Error、Fatal 中的具体哪一个,是根据当前的这个问题对以后可能产生的影响而定的,如果对以后基本没什么影响,则警告之,如果肯定是以后要出严重问题的了,则Fatal之,拿不准会怎么样,则 Error 之。
=== 一些疑惑 ===
不过在实际使用中,基于上面的这种考虑,也还是有一些具体问题。最常见的就是要在最终产品中将输出日志打开到那种级别才算好呢?
例如在应用中有一个输出窗口,一些系统状态信息将被输出到这个输出窗口中。因为 Info 的级别是如此之低,所以为了让用户能够看到有效的输出信息,必须将日志级别开放到 Info 级别。但是 Warn 的级别比 Info 要高,所以用户不得不被迫看到一些 Warn 的信息。而我们其实已经假定,Warn 信息其实并不影响系统的正常运行,这一般只代表系统中存在一些还没有被发现或者修改的小 Bug。这些 Warn 信息会让最终用户困惑甚至恐慌,系统发出警告了,该怎么办?
个人观点,Info 的级别应该比 Warn 更高才对,Warn 信息和 Debug 一样,应该在产品测试和调试时使用,而 Info、Erro 以及 Fatal 则在产品发布后需要继续使用。
目前我所采用的解决方法是,对于 Warn、Error、Fatal 都添加一个相应的系统断言,这样,可以保证当发生这种问题时,在调试阶段,可以立即得到提示。在软件发布以后,这些信息也能被记录到日志文件中去。
{{{
log.Warn("message");
System.Diagnostics.Debug.Fail("警告", "message");
}}}
Debug.Fail 将导致编译为 Debug 输出时,会弹出一个消息警告窗口,这可保证在测试、调试阶段不漏过任何一个潜在的错误。而在发布时,Release 编译的输出不会包括 Debug 语句,这就不会打扰最终用户,而错误信息仍然能通过 log 记录到日志中
=== Warn、Error、Fatal ===
警告、错误、严重错误,这三者应该都在系统运行时检测到了一个不正常的状态,他们之间的区别,要区分还真不是那么简单的事情。我大致是这样区分的:
所谓警告,应该是这个时候进行一些修复性的工作,应该还可以把系统恢复到正常状态中来,系统应该可以继续运行下去。
所谓错误,就是说可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段,很可能会因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止也不出现严重问题。
所谓Fatal,那就是相当严重的了,可以肯定这种错误已经无法修复,并且如果系统继续运行下去的话,可以肯定必然会越来越乱。这时候采取的最好的措施不是试图将系统状态恢复到正常,而是尽可能地保留系统有效数据并停止运行。
也就是说,选择 Warn、Error、Fatal 中的具体哪一个,是根据当前的这个问题对以后可能产生的影响而定的,如果对以后基本没什么影响,则警告之,如果肯定是以后要出严重问题的了,则Fatal之,拿不准会怎么样,则 Error 之。
=== 一些疑惑 ===
不过在实际使用中,基于上面的这种考虑,也还是有一些具体问题。最常见的就是要在最终产品中将输出日志打开到那种级别才算好呢?
例如在应用中有一个输出窗口,一些系统状态信息将被输出到这个输出窗口中。因为 Info 的级别是如此之低,所以为了让用户能够看到有效的输出信息,必须将日志级别开放到 Info 级别。但是 Warn 的级别比 Info 要高,所以用户不得不被迫看到一些 Warn 的信息。而我们其实已经假定,Warn 信息其实并不影响系统的正常运行,这一般只代表系统中存在一些还没有被发现或者修改的小 Bug。这些 Warn 信息会让最终用户困惑甚至恐慌,系统发出警告了,该怎么办?
软件中总免不了要使用诸如 Log4net, Log4j, Tracer 等东东来写日志,不管用什么,这些东东大多是大同小异的,一般都提供了这样5个日志级别:
× Debug
× Info
× Warn
× Error
× Fatal
一个等级比一个高,但是在具体开发中,关于应该如何选择适应的等级,却没有找到好的文章进行说明。记录一下自己的一些看法,以便日后使用吧。
=== Debug ===
这个级别最低的东东,一般的来说,在系统实际运行过程中,一般都是不输出的。
因此这个级别的信息,可以随意的使用,任何觉得有利于在调试时更详细的了解系统运行状态的东东,比如变量的值等等,都输出来看看也无妨。
当然,在每一个 Debug 调用之前,一定要加上 If 判断。
=== Info ===
这个应该用来反馈系统的当前状态给最终用户的,所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。
从某种角度上说,Info 输出的信息可以看作是软件产品的一部分(就像那些交互界面上的文字一样),所以需要谨慎对待,不可随便。
=== Warn、Error、Fatal ===
警告、错误、严重错误,这三者应该都在系统运行时检测到了一个不正常的状态,他们之间的区别,要区分还真不是那么简单的事情。我大致是这样区分的:
所谓警告,应该是这个时候进行一些修复性的工作,应该还可以把系统恢复到正常状态中来,系统应该可以继续运行下去。
所谓错误,就是说可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段,很可能会因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止也不出现严重问题。
所谓Fatal,那就是相当严重的了,可以肯定这种错误已经无法修复,并且如果系统继续运行下去的话,可以肯定必然会越来越乱。这时候采取的最好的措施不是试图将系统状态恢复到正常,而是尽可能地保留系统有效数据并停止运行。
也就是说,选择 Warn、Error、Fatal 中的具体哪一个,是根据当前的这个问题对以后可能产生的影响而定的,如果对以后基本没什么影响,则警告之,如果肯定是以后要出严重问题的了,则Fatal之,拿不准会怎么样,则 Error 之。
=== 一些疑惑 ===
不过在实际使用中,基于上面的这种考虑,也还是有一些具体问题。最常见的就是要在最终产品中将输出日志打开到那种级别才算好呢?
例如在应用中有一个输出窗口,一些系统状态信息将被输出到这个输出窗口中。因为 Info 的级别是如此之低,所以为了让用户能够看到有效的输出信息,必须将日志级别开放到 Info 级别。但是 Warn 的级别比 Info 要高,所以用户不得不被迫看到一些 Warn 的信息。而我们其实已经假定,Warn 信息其实并不影响系统的正常运行,这一般只代表系统中存在一些还没有被发现或者修改的小 Bug。这些 Warn 信息会让最终用户困惑甚至恐慌,系统发出警告了,该怎么办?
个人观点,Info 的级别应该比 Warn 更高才对,Warn 信息和 Debug 一样,应该在产品测试和调试时使用,而 Info、Erro 以及 Fatal 则在产品发布后需要继续使用。
目前我所采用的解决方法是,对于 Warn、Error、Fatal 都添加一个相应的系统断言,这样,可以保证当发生这种问题时,在调试阶段,可以立即得到提示。在软件发布以后,这些信息也能被记录到日志文件中去。
{{{
log.Warn("message");
System.Diagnostics.Debug.Fail("警告", "message");
}}}
Debug.Fail 将导致编译为 Debug 输出时,会弹出一个消息警告窗口,这可保证在测试、调试阶段不漏过任何一个潜在的错误。而在发布时,Release 编译的输出不会包括 Debug 语句,这就不会打扰最终用户,而错误信息仍然能通过 log 记录到日志中
个人观点,Info 的级别应该比 Warn 更高才对,Warn 信息和 Debug 一样,应该在产品测试和调试时使用,而 Info、Erro 以及 Fatal 则在产品发布后需要继续使用。
目前我所采用的解决方法是,对于 Warn、Error、Fatal 都添加一个相应的系统断言,这样,可以保证当发生这种问题时,在调试阶段,可以立即得到提示。在软件发布以后,这些信息也能被记录到日志文件中去。
{{{
log.Warn("message");
System.Diagnostics.Debug.Fail("警告", "message");
}}}
Debug.Fail 将导致编译为 Debug 输出时,会弹出一个消息警告窗口,这可保证在测试、调试阶段不漏过任何一个潜在的软件中总免不了要使用诸如 Log4net, Log4j, Tracer 等东东来写日志,不管用什么,这些东东大多是大同小异的,一般都提供了这样5个日志级别:
× Debug
× Info
× Warn
× Error
× Fatal
一个等级比一个高,但是在具体开发中,关于应该如何选择适应的等级,却没有找到好的文章进行说明。记录一下自己的一些看法,以便日后使用吧。
=== Debug ===
这个级别最低的东东,一般的来说,在系统实际运行过程中,一般都是不输出的。
因此这个级别的信息,可以随意的使用,任何觉得有利于在调试时更详细的了解系统运行状态的东东,比如变量的值等等,都输出来看看也无妨。
当然,在每一个 Debug 调用之前,一定要加上 If 判断。
=== Info ===
这个应该用来反馈系统的当前状态给最终用户的,所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。
从某种角度上说,Info 输出的信息可以看作是软件产品的一部分(就像那些交互界面上的文字一样),所以需要谨慎对待,不可随便。
=== Warn、Error、Fatal ===
警告、错误、严重错误,这三者应该都在系统运行时检测到了一个不正常的状态,他们之间的区别,要区分还真不是那么简单的事情。我大致是这样区分的:
所谓警告,应该是这个时候进行一些修复性的工作,应该还可以把系统恢复到正常状态中来,系统应该可以继续运行下去。
所谓错误,就是说可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段,很可能会因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止也不出现严重问题。
所谓Fatal,那就是相当严重的了,可以肯定这种错误已经无法修复,并且如果系统继续运行下去的话,可以肯定必然会越来越乱。这时候采取的最好的措施不是试图将系统状态恢复到正常,而是尽可能地保留系统有效数据并停止运行。
软件中总免不了要使用诸如 Log4net, Log4j, Tracer 等东东来写日志,不管用什么,这些东东大多是大同小异的,一般都提供了这样5个日志级别:
× Debug
× Info
× Warn
× Error
× Fatal
一个等级比一个高,但是在具体开发中,关于应该如何选择适应的等级,却没有找到好的文章进行说明。记录一下自己的一些看法,以便日后使用吧。
=== Debug ===
这个级别最低的东东,一般的来说,在系统实际运行过程中,一般都是不输出的。
因此这个级别的信息,可以随意的使用,任何觉得有利于在调试时更详细的了解系统运行状态的东东,比如变量的值等等,都输出来看看也无妨。
当然,在每一个 Debug 调用之前,一定要加上 If 判断。
=== Info ===
这个应该用来反馈系统的当前状态给最终用户的,所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。
从某种角度上说,Info 输出的信息可以看作是软件产品的一部分(就像那些交互界面上的文字一样),所以需要谨慎对待,不可随便。
=== Warn、Error、Fatal ===
警告、错误、严重错误,这三者应该都在系统运行时检测到了一个不正常的状态,他们之间的区别,要区分还真不是那么简单的事情。我大致是这样区分的:
所谓警告,应该是这个时候进行一些修复性的工作,应该还可以把系统恢复到正常状态中来,系统应该可以继续运行下去。
所谓错误,就是说可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段,很可能会因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止也不出现严重问题。
所谓Fatal,那就是相当严重的了,可以肯定这种错误已经无法修复,并且如果系统继续运行下去的话,可以肯定必然会越来越乱。这时候采取的最好的措施不是试图将系统状态恢复到正常,而是http://www.cnblogs.com/shwen99/rss尽可能地保留系统有效数据并停止运行。
也就是说,选择 Warn、Error、Fatal 中的具体哪一个,是根据当前的这个问题对以后可能产生的影响而定的,如果对以后基本没什么影响,则警告之,如果肯定是以后要出严重问题的了,则Fatal之,拿不准会怎么样,则 Error 之。
=== 一些疑惑 ===
不过在实际使用中,基于上面的这种考虑,也还是有一些具体问题。最常见的就是要在最终产品中将输出日志打开到那种级别才算好呢?
例如在应用中有一个输出窗口,一些系统状态信息将被输出到这个输出窗口中。因为 Info 的级别是如此之低,所以为了让用户能够看到有效的输出信息,必须将日志级别开放到 Info 级别。但是 Warn 的级别比 Info 要高,所以用户不得不被迫看到一些 Warn 的信息。而我们其实已经假定,Warn 信息其实并不影响系统的正常运行,这一般只代表系统中存在一些还没有被发现或者修改的小 Bug。这些 Warn 信息会让最终用户困惑甚至恐慌,系统发出警告了,该怎么办?
个人观点,Info 的级别应该比 Warn 更高才对,Warn 信息和 Debug 一样,应该在产品测试和调试时使用,而 Info、Erro 以及 Fatal 则在产品发布后需要继续使用。
目前我所采用的解决方法是,对于 Warn、Error、Fatal 都添加一个相应的系统断言,这样,可以保证当发生这种问题时,在调试阶段,可以立即得到提示。在软件发布以后,这些信息也能被记录到日志文件中去。
{{{
log.Warn("message");
System.Diagnostics.Debug.Fail("警告", "message");
}}}
Debug.Fail 将导致编译为 Debug 输出时,会弹出一个消息警告窗口,这可保证在测试、调试阶段不漏过任何一个潜在的错误。而在发布时,Release 编译的输出不会包括 Debug 语句,这就不会打扰最终用户,而错误信息仍然能通过 log 记录到日志中
也就是说,选择 Warn、Error、Fatal 中的具体哪一个,是根据当前的这个问题对以后可能产生的影响而定的,如果对以后基本没什么影响,则警告之,如果肯定是以后要出严重问题的了,则Fatal之,拿不准会怎么样,则 Error 之。
=== 一些疑惑 ===
不过在实际使用中,基于上面的这种考虑,也还是有一些具体问题。最常见的就是要在最终产品中将输出日志打开到那种级别才算好呢?
例如在应用中有一个输出窗口,一些系统状态信息将被输出到这个输出窗口中。因为 Info 的级别是如此之低,所以为了让用户能够看到有效的输出信息,必须将日志级别开放到 Info 级别。但是 Warn 的级别比 Info 要高,所以用户不得不被迫看到一些 Warn 的信息。而我们其实已经假定,Warn 信息其实并不影响系统的正常运行,这一般只代表系统中存在一些还没有被发现或者修改的小 Bug。这些 Warn 信息会让最终用户困惑甚至恐慌,系统发出警告了,该怎么办?
个人观点,Info 的级别应该比 Warn 更高才对,Warn 信息和 Debug 一样,应该在产品测试和调试时使用,而 Info、Erro 以及 Fatal 则在产品发布后需要继续使用。
目前我所采用的解决方法是,对于 Warn、Error、Fatal 都添加一个相应的系统断言,这样,可以保证当发生这种问题时,在调试阶段,可以立即得到提示。在软件发布以后,这些信息也能被记录到日志文件中去。
{{{
log.Warn("message");
System.Diagnostics.Debug.Fail("警告", "message");
}}}
Debug.Fail 将导致编译为 Debug 输出时,会弹出一个消息警告窗口,这可保证在测试、调试阶段不漏过任何一个潜在的错误。而在发布时,Release 编译的输出不会包括 Debug 语句,这就不会打扰最终用户,而错误信息仍然能通过 log 记录到日志中错误。而在发布时,Release 编译的输出不会包括 Debug 语句,这就不会打扰最终用户,而错误信息仍然能通过 log 记录到日志中
目前我所采用的解决方法是,对于 Warn、Error、Fatal 都添加一个相应的系统断言,这样,可以保证当发生这种问题时,在调试阶段,可以立即得到提示。在软件发布以后,这些信息也能被记录到日志文件中去。
{{{
log.Warn("message");
System.Diagnostics.Debug.Fail("警告", "message");
}}}
Debug.Fail 将导致编译为 Debug 输出时,会弹出一个消息警告窗口,这可保证在测试、调试阶段不漏过任何一个潜在的错误。而在发布时,Release 编译的输出不会包括 Debug 语句,这就不会打扰最终用户,而错误信息仍然能通过 log 记录到日志中
http://www.rainlendar.net/cms/index.php?option=com_rny_download&Itemid=32
listener.ora、 tnsnames.ora和sqlnet.ora这3个文件是关系oracle网络配置的3个主要文件,其中listener.ora是和数据库服务器端 相关,而tnsnames.ora和sqlnet.ora这2个文件不仅仅关系到服务器端,主要的还是和客户端关系紧密。
检查客户端oracle网络的时候可以先检查sqlnet.ora文件:
# SQLNET.ORA Network Configuration File: $ORACLE_HOME/NETWORK/ADMIN/sqlnet.ora
# Generated by Oracle configuration tools.
SQLNET.AUTHENTICATION_SERVICES= (NTS)
NAMES.DIRECTORY_PATH= (TNSNAMES, HOSTNAME, ONAMES)
##NAMES.DEFAULT_DOMAIN = us.oracle.com
上面的sqlnet.ora文件说明:
SQLNET.AUTHENTICATION_SERVICES= (NTS)——这个表示采用os认证,在数据库服务器上,可以利用sqlplus “/ as sysdba”。一般这个配置在windows上是ok的,在unix环境下可能会有问题,一般在unix下可以去掉这个配置。
NAMES.DIRECTORY_PATH= (TNSNAMES, HOSTNAME, ONAMES)——表示将首先利用tnsnames进行解析;如果tnsnames解析不到,将使用hostname解析;如果hostname解析不 到,将采用onames进行解析。
被注释掉的NAMES.DEFAULT_DOMAIN = us.oracle.com——表示采用默认的domain name为us.oracle.com,在tnsnames.ora中如果配置对应的解析,如果原来的别名oralocal,那么,当启用这个参数后,在 tnsnames中的配置要改成oralocal.us.oracle.com。在使用tnsping时或者sqlplus登录时,只需写前面的别名,系 统会自动加上后面的domain name来进行解析。
检查完毕sqlnet.ora,一般都会发现是使用tnsname来解析别名的,那么,tnsnames.ora中可以有哪些配置种类呢?
# TNSNAMES.ORA Network Configuration File: $ORACLE_HOME/NETWORK/ADMIN/tnsnames.ora
# Generated by Oracle configuration tools.
###### 一般的配置 ##################################
ORALOCAL =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 127.0.0.1)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = oralocal)
)
)
###### 这样也行,用SID=oralocal ###########################
ORALOCAL_2 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 127.0.0.1)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SID = oralocal)
)
###### RAC的配置(3节点rac) ###############################
ORALOCAL =
(DESCRIPTION =
(load_balance = yes)
(failover = on)
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.1)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.2)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.3)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = oralocal)
(SERVER = DEDICATED)
(failover_mode=(type=select)(method=basic)(retries=20)(delay=20))
)
)
ORALOCAL_NODE1 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.1)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = oralocal)
(INSTANCE_NAME = oralocal_node1)
)
)
ORALOCAL_NODE2=
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.2)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = oralocal)
(INSTANCE_NAME = oralocal_node2)
)
)
ORALOCAL_NODE3 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.3)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = oralocal)
(INSTANCE_NAME = oralocal_node3)
)
)
)
###### DATA GUARD配置(primary库和standby库都需要配置)##############
standby =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.2.2)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER=DEDICATED)
(SERVICE_NAME = oralocal)
)
)
primary =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.2.1)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER=DEDICATED)
(SERVICE_NAME = oralocal)
)
)
另外需要注意的2点情况:
(1)如果tnsnames中的service_name配置错误,配置成了instance_name了,这个时候会发生tnsping能通,但是 sqlplus连接不上的奇怪情况。报错ORA-12514: TNS:listener could not resolve SERVICE_NAME given in connect descriptor。这个时候查错的时候,需要检查对应的service_name。
(2)如果远程数据库是rac,而且本地客户端端远程数据库处于不同的网段,通过公网链接,rac对外的ip映射只有一个,即只映射到一个节点。请注意在 客户端配置tnsnames的时候按照单机的情况来配置。呵呵,dba不仅仅要学习oracle,了解一些网络的知识,特别是自己系统的网络架构,也是需 要的。
1. 开-闭原则(对扩展开放,对修改关闭)
2. 里氏替换原则(父类的方法都要在子类中实现或者重写)
3. 依赖倒转原则(要针对接口编程,不要针对实现编程)
4. 接口隔离原则
5. 合成/聚合复用原则(尽量使用合成/聚合,而不是使用继承)
6. 迪米特法则(talk only to your immediate friends)
用下面命令:
sudo rm /etc/fonts/conf.d/49-sansserif.conf 在应用程序->附件->终端 中执行就可以了.已经测试
准备工作:下载httpd.2.2.9.tar.gz
安装apache2.2
tar zxvf httpd.2.2.9.tar.gz
cd httpd.2.2.9
./configure --enable-dav --enable-so --prefix=/opt/apache2.2
说明:--enable-dav允许Apache提供DAV协议支持;--enable-so允许运行时加载DSO模块, --prefix指定apache的安装目录
make
make install
说明:在configure与make很可能会出现一些fail,而往往是因为缺少一些必要的库,就直接安装一下apt-get install *****,即可,我安装出现过三个,反正这个比较直观
这样,apache 应该安装好了,测试一下,/opt/apache2/bin/apachectl -k start, 在浏览器里输入:http://locahost
正常情况下应该出现It works!
1.首先下载
ubuntu-9.10-desktop-i386.iso(
http://www.ubuntu.com/getubuntu/download)文件
存放在C盘(ntfx/fat32)的根目录下;
2.下载Grub4rDos(
http://sourceforge.net/projects/grub4dos),将grldr和grub.exe放入C盘根目录;
3.从
ubuntu-9.10-desktop-i386.iso文件中复制menu.lst文件到c盘根目录,在最后加入如下内容:
title Install ubuntu
find --set-root /
ubuntu-9.10-desktop-i386.iso
kernel /vmlinuz boot=casper find_iso=/
ubuntu-9.10-desktop-i386.iso
initrd /initrd.gz
4.复制
ubuntu-9.10-desktop-i386.iso里 Casper和 .disk两个目录到C盘根目录;
5.复制
ubuntu-9.10-desktop-i386.iso里Casper目录中的vmlinuz和initrd.lz文件到C盘根目录,将initrd.lz文件修改为initrd.gz
6.修改C盘根目录下的boot.ini文件,在文件末尾加入下面代码:
C:\grldr="GRUB"
7.重新启动,在启动菜单处,选择Grub,然后再选择Install Ubuntu就可以开始安装了
8.安装时选择手动分区,可以保留原来的windows系统
9.可能会无法卸载isodevice分区(我遇到了)到挂载的时候,同时按下Ctrl+Alt+F2,然后在:后输入sudo umount -l /isodevice 输好后,然后回车,再同时按下 Ctrl+Alt+F7,回到安装界面
基本上就ok了
近几年,随着LDAP(Light Directory Access
Protocol,轻量级目录访问协议)技术的兴起和应用领域的不断扩展,目录服务技术成为许多新型技术实现信息存储、管理和查询的首选方案,特别是在网
络资源查找、用户访问控制与认证信息的查询、新型网络服务、网络安全、商务网的通用数据库服务和安全服务等方面,都需要应用目录服务技术来实现一个通用、
完善、应用简单和可以扩展的系统。
对于任何一家大IT网络的企业来说,IT系统中的目录服务功能是必不可少的。如果一个在全国有多个分支机构的企业,已经有了一个内部网络系统,每一个分支
机构都有一个局域网,局域网之间通过专线或者VPN通道连接在一起,那么,如何将网络中的资源和信息有效地管理起来呢?通常,这个企业可以在每一个分支机
构或者每个城市建立一个目录服务器,任何地方的员工连接到本地目录服务器就可以访问到目录树中所有的信息,在目录服务器之间复制目录信息,以保持同步。比
如,人事部门看到的人员目录与财务部门、设备管理部门看到的人员目录是完全一致的,他们所使用的应用系统无须再建立另一套目录结构。当然,这一切都是要经
过身份验证的。
目录服务有着如此重要的作用,但在过去,企业通常采用基于Windows的目录服务器,Linux在这方面相形逊色。作为Windows的核心内容,目录
服务被企业IT人员认为是Windows与Linux相比最具竞争力的部分,也成为Linux产品架构中的软肋。随着Red Hat
Enterprise Linux 4.0出现,这个情况已经改变了。RHEL 4 内附的LDAP 服务器为OpenLDAP 2.2.13-2
版,OpenLDAP 2.x包括数个重要功能:
1. 支持LDAPv3 - OpenLDAP 2.0
除了其它改善外还支持SASL(SimpleAuthentication and Security Layer)、TLS(Transport
Layer Security)以及SSL(Secure Sockets Layer)。LDAPv2 之后通讯协议很多的改变都是为了加强LDAP
的安全性。
2. 支持IPv6 - OpenLDAP 支持新一代的因特网通讯协议第6 版。
3. LDAP Over IPC - OpenLDAP 能够使用IPC 在系统内进行通讯。这可以避免使用网络通讯以增加安全性。
4. 使用新的应用程序界面: 改善程序设计人员联机及使用程序的方法。
本文将以Red Hat Enterprise Linux 4.0 为例,介绍在Linux平台使用OpenLDAP上建立目录服务器。
一、LDAP协议简介
LDAP(轻
量级目录访问协议,Lightweight Directory Access
Protocol)是实现提供被称为目录服务的信息服务。目录服务是一种特殊的数据库系统,其专门针对读取,浏览和搜索操作进行了特定的优化。目录一般用
来包含描
述性的,基于属性的信息并支持精细复杂的过滤能力。目录一般不支持通用数据库针对大量更新操作操作需要的复杂的事务管理或回卷策略。而目录服务的更新则一
般都非常简单。这种目录可以存储包括个人信息、web链结、jpeg图像等各种信息。为了访问存储在目录中的信息,就需要使用运行在TCP/IP之上的访
问协议—LDAP。
LDAP目录中的信息是是按照树型结构组织,具体信息存储在条目(entry)的数据结构中。条目相当于关系数据库中表的记录;条目是具有区别名
DN(Distinguished
Name)的属性(Attribute),DN是用来引用条目的,DN相当于关系数据库表中的关键字(Primary
Key)。属性由类型(Type)和一个或多个值(Values)组成,相当于关系数据库中的字段(Field)由字段名和数据类型组成,只是为了方便检
索的需要,LDAP中的Type可以有多个Value,
而不是关系数据库中为降低数据的冗余性要求实现的各个域必须是不相关的。LDAP中条目的组织一般按照地理位置
和组织关系进行组织,非常的直观。LDAP系统结构图见图1.
图1 LDAP系统结构图
LDAP的信息是以树型结构存储的,在树根一般定义国家(c=CN)或域名(dc=com),在其下则往往定义一个或多个组织
(organization)(o=Acme)或组织单元(organizational units)
(ou=People)。一个组织单元可能包含诸如所有雇员、 大楼内的所有打印机等信息。
此外,LDAP支持对条目能够和必须支持哪些属性进行控制,这是有一个特殊的称为对
象类别(objectClass)的属性来实现的。该属性的值决定了该条目必须遵循的一些规则,其规定了该条目能够及至少应该包含哪些属性。例
如:inetorgPerson对象类需要支持sn(surname)和cn(common
name)属性,但也可以包含可选的如邮件,电话号码等属性。dn :一条记录的位置;dc :一条记录所属区域;ou
:一条记录所属组织;cn/uid:一条记录的名字/ID。OpenLdap是一个正在得到日益普遍应用的开源软件,和LADP完全兼容。
二、安装OpenLDAP服务器
如
果在系统安装时已经把安装上了,那么我们就可以直接对OpenLDAP进行配置使用了。否则,可以通过Rat Het Enterprise
Linux图形界面下的“添加/删除应用程序”工具进行安装。具体方法是,选择“主选单”→“系统设置”→“添加/删除应用程序”,在弹出的界面中选中
“网络服务器”的“OpenLDAP-server”,单击“更新”即可,见图2。
图2 安装OpenLDAP 服务器软件
如果你使用的是其他版本的Linux,那么通常要安装以下软件包:OpenLDAP、 OpenLDAP-servers、
OpenLDAP-clients、 OpenLDAP-devel
,OpenLDAP-2.0是必要套件,一定要先安装;OpenLDAP-servers是服务器套件;OpenLDAP-clients是操作程序套
件;OpenLDAP-devel是开发工具套件。
三、配置OpenLDAP 服务器
以RedHat Linux 4所为例字介绍OpenLDAP 服务器配置文件。主要文件见表1。
表1
1. 建立Linux用户账号
使用文本编辑建立一个文本文件,文件名称myusers.list 内容如下:
user1 123456
user2 123456
user3 123456
user4 123456
user5 123456
user6 123456
user7 123456
user8 123456
user9 123456
注意:第一个字段为使用者名称;第二个字段为预设密码,中间必须用空格隔开。然后使用文本编辑建立另外一个文本文件,文件名称add-users.sh内容如下:
#!/bin/bash
for i in `awk '{print $1}' users.list `
do
useradd $i
grep "<$i>" users.list | awk '{print $2}' | passwd --stdin $i
done
建立Linux用户账号:
#chmod 775 add-users.sh
#./add-users.sh
2.修改缺省配置文件:/etc/OpenLDAP/slapd.conf,请把蓝色部分按照您的具体情况填写。
database bdb
suffix "dc=myexample,dc=com" #一条记录所属区域#
rootdn "cn=Manager,dc=example,dc=com"
rootpw 1234567 #定义LDAP根管理员的密码
3.将原有Linux 账号转为LDIF 文件
原有Linux 服务器上有user1-user9 这些使用者账号,密码均为123456;面便是转换的步骤:
# cd /usr/share/OpenLDAP/migration #转换文件的目录#
# vi migrate_common.ph
$DEFAULT_MAIL_DOMAIN = "myexample.com";
Default base
$DEFAULT_BASE = "dc=myexample,dc=com";
# ./migrate_passwd.pl /etc/passwd > /worktmp/user.ldif
# ./migrate_group.pl /etc/group > /worktmp/group.ldif
4. 建立example.ldif,ou_people.ldif, ou_group.ldif三个文件
#cat example.ldif
dn: dc=example,dc=com
dc: example
objectClass: dcObject
objectClass: organizationalUnit
ou: example.com
#cat ou_people.ldif
dn: ou=people, dc=example, dc=com
objectclass: organizationalunit
ou: people
#cat ou_group.ldif
dn: ou=group, dc=example, dc=com
objectclass: organizationalunit
ou: group
5. 转换原有Linux 账号至OpenLDAP服务器上:
#slapadd -vl example.ldif
added: "dc=example,dc=com" (00000001)
#slapadd -vl ou_people.ldif
added: "ou=people,dc=example,dc=com" (00000002)
#slapadd -vl ou_group.ldif
added: "ou=group,dc=example,dc=com" (00000043)
#slapadd -vl user.ldif
#slapadd -vl group.ldif
四、启动OpenLDAP服务器
#chown ldap.ldap /var/lib/ldap/* #把/var/lib/ldap/目录内的档案变更拥有者及群组为ldap。
然后可以通过Rat Het Enterprise Linux图形界面下的选择“主选单”→“系统设置”→“服务器设置”-“服务”,在弹出的界面中选中ldap”,单击“重新启动”即可,见图3。
图3 服务器启动界面
利用ldapsearch 指令可搜寻LDAP 服务器的数据,若是可看到以下的数据,代表整个设定正确无误。
# ldapsearch -x -b "dc=example,dc=com"
………
# user9, Group, myexample.com
dn: cn=user9,ou=Group,dc=myexample,dc=com
objectClass: posixGroup
objectClass: top
cn: user9
userPassword:: e2NyeXB0fXg=
gidNumber: 508
………
五、配置Linux OpenLDAP客户端
在客户端执行authconfig-gtk命令,进入认证配置界面,进入图4所示的界面中配置LDAP服务器的信息。在LDAP 服务器处 指定 LDAP 服务器的 IP 地址。
图4 添加OpenLDAP服务器IP地址
打开 /etc/ldap.conf 文件,下面是一些用于配置的关键指令。
到此为止我们已经配置完成Liunx OpenLDAP目录服务器、客户端。
要安装 MySQL,可以在终端提示符后运行下列命令:
sudo apt-get install mysql-server
sudo apt-get install mysql-client
sudo apt-get install php5-mysql
// 安装php5-mysql 是将php和mysql连接起来
一旦安装完成,MySQL 服务器应该自动启动。您可以在终端提示符后运行以下命令来检查 MySQL 服务器是否正在运行:
sudo netstat -tap | grep mysql
当您运行该命令时,您可以看到类似下面的行:
tcp 0 0 localhost.localdomain:mysql *:* LISTEN -
如果服务器不能正常运行,您可以通过下列命令启动它:
sudo /etc/init.d/mysql restart
进入mysql
mysql -uroot -p
(输入mysql的root密码)
配置 MySQL 的管理员密码:
(输入mysql的root密码)
配置 MySQL 的管理员密码:
我还在想象着我的18岁,可我却马上30岁了!真的吗?不愿意去想,但不得不面对。
曾经的年少轻狂,早已荡然无存。曾经的花样年华,早已悄然而逝。80后的我们已经开始站在了20岁的尾巴上,面对三十而立,还有多少人可以昂着头,信誓旦
旦地说,我们依然年轻?是的,年轻就是资本,面对着90后的异军突起,80后的我们是否还年轻?
曾经,骄傲的我们都怀抱着崇高的理想,奔走在陌生的城市,只为寻找内心深处最真的梦想。曾经,生活得再艰难,都会想着只要自己努力,就一定可以取得成
功。曾经,单纯的认为,就算很小很小的一个房间,都可以经营自己最美的爱情。当事业依然碌碌无为的时候,当爱情变得虚无缥缈的时候,80后的我们,是不是
依然还会说,年轻不怕失败?
终有一天,我才发现,原来,80后的我们,都早已经老去。我们不再轻狂,我们不再潇洒,我们不再坦荡,我们不再微笑,我们有的是对于生活的压力,我们有的是对于婚姻的恐惧,我们有的是对于未来的失望,我们有的是对于困难的却步.
面对工作:我们已经没有了更多的激情,只希望能够有一份足够安稳的工作。有风险的工作都已经被排斥了,曾经一千个一万个不愿意走关系找工作的我们,面
对着事业的平淡,连收入都无法给予自己温饱的时候,我们不得不选择屈服,放下原本最高傲的脸面,开始寻求人际关系的最大化。工作的艰辛,对于我们无所畏
惧,重要的是可以一直将这份工作持续。
面对感情:已经不把婚姻当成爱情的升华,而是把婚姻当作是亲情。我们不再渴望一份浪漫而刺激的爱情,而是奢望一段幸福而美满的婚姻。曾经固执的以为,
相亲那只不过是70后,或者说更早一代的婚姻方式,然而等到现在才发现,相亲已经成为了80后婚姻的主流方式。一场场地相亲,一次次的绝望,已经对相亲产
生了麻木和排斥,但依然奔波在相亲场上。难怪,一个朋友说:“我不是在相亲,就是在相亲的路上。”
面对穿着:曾经过度讲究品牌感觉的我们,对于品牌已经变得陌生,橱柜里的昂贵的衣服,让工薪阶层的我们没有勇气再去试穿。赚钱之后,才知道,原来生活
是多么的不容易。我们不再讲究品牌,而更注重衣服的质量以及用途,适合穿着的场合,不再会因为一时的冲动,去血拼那些打折的衣服,衣服不在于多,而在于体
面。合适的,才是我们所需要的,品牌只不过是一个虚幻的东西。
面对社交:当越来越多的朋友有了家庭之后,已经没有多大的勇气再和他们一起聊聊关于生活,因为在他们的口中,都是关于家庭的,而单身的80后更注重个
人情感的书法。曾经极度热爱的酒吧、KTV都不再喜欢了,已经忍受不了那种嘈杂、疯狂的环境,更喜欢坐在咖啡厅里或者茶馆里,看看书,听听音乐,享受咖啡
的苦涩,亦或者茶的清香。
面对家庭:家庭超越了爱情和友情,一切都将家庭放在第一位。没有了原本的冲动,了解了父母的苦心,懂得去体谅他们的无可奈何。原本单纯的家庭之间的关
系,在某一天突然变得清晰,了解了人与人之间的关系,原来一直都不那么单纯,最值得相信的,除了父母,还有自己。越来越讨厌,走亲访友,因为总是会有很多
人问题,关于婚姻的某个问题。
面对娱乐:突然发现,手机使用的频率越来越少了,短信也越来越少了,有的时候甚至不愿意发短信,宁可打个电话,匆匆挂掉。QQ也不像以前那样拼命地闪
个不停,退出了许多的群,有些群碍于面子,一直处于屏蔽,只是偶尔选择几个群聊几句就隐身。再也不会为了游戏,废寝忘食地玩,更多的时候会捧着一本书,安
静地看着。
面对购物:总会想着买东西的时候,是不是有什么赠品,而不会看着哪个好看就买哪个。不再会像以前那样,为了得到某样赠品,去买某样自己不喜欢的东西。
购物早已不是逛街了,而变成了有目的性的行为,买完自己想要的东西,就匆匆离开。一些漂亮,精致的小摆设,只是在柜台上欣赏完之后,放回去,再也不会带回
家之后,塞进箱底。
面对一切的一切,我们都已经会了深思熟虑,我们的心中,早已没有了童话。我们浪费掉了太多的青春,那是一段如此自以为是、又如此狼狈不堪的青春岁月。有欢笑,也有泪水;有朝气,也有颓废;有甜蜜,也有荒唐;有自信,也有迷茫。
我们敏感,我们偏执,我们顽固到底地故作坚强;我们轻易的伤害别人,也轻易的被别人所伤。
我们追逐于颓废的快乐,陶醉于寂寞的美丽;
我们坚信自己与众不同,坚信世界会因我而改变;
我们觉醒其实我们已经不再年轻,我们前途或许也不再是无限的,其实它又何曾是无限的?
80后的我们,已经开始承认我们老了,没有了90后的青春,没有90后的激情,我们有的只是为了生活进行奔波着的疲惫的心。偶尔微笑着告诉自己:三十
岁的男人,我的美好年华才刚刚开始。80后的我们,打一场酣畅淋漓球,也都心有余而力不足。很久没有运动,开始显露臃肿的啤酒肚。
我不禁感叹:原来,80后的我们都早已老去,只是我们一直不愿意服老。
“今天,之所以区别于昨天,恰恰是因为昨天的感受依然在我心中。”