不止一次我們的項目在靠開發人員硬扛著,bug來不及修改,文檔還在猛補,項目經理又在催著出版本,每修改一個bug都要在代碼的泥沼中摸爬滾打半天,卻又在制造著另外的bug,為了文檔而制造著根本沒有參考價值的文檔。每發布一個版本都要瞻前顧后并且總要無休止的加班,似乎開發人員永遠有干不完的活。
我們不禁問自己,為什么會有那么多失敗的軟件,軟件開發我們到底還需要什么?
工具?我們不乏偉大的工具,IDE我們有eclipse、intelliJ、VC++等等,建模我們有Rose、together、Visio等等,配置管理我們Subversion、CVS、ClearCase等等,bug管理我們有ClearQuest、bugzilla等等,文檔我們word、wps等,還有集成、測試甚至生成代碼等等我們都有偉大的工具,工具我們不缺。流程?瀑布模型、迭代模型、UP、XP我們不缺流程管理的理論。知識?軟件開發算是一項知識型的工作,我們的開發人員一般都是本科碩士畢業,況且搜索引擎如此發達的今天,知識根本不是問題,況且我們的軟件開發需要多么高深的知識嗎?
優秀的理論支持、有能力的人員、先進的工具,這些我們都具備,我們所缺的只是一種軟件開發的理念,缺少軟件開發的情商。我們一開始學習編程知識接觸到的就是C語言和數據結構,慢慢的一些結構化的思想就扎根于大腦,其實在商業軟件中數據結構、算法很少涉及,我們所需要的僅僅是一種設計、開發的理念。比如用面向對象本來是一種簡單的思想,目的是為了降低軟件的復雜性而出現的,可是讓熟悉了結構化編程的人去搞反而覺得很難。一些好的實踐經驗我們也經常提到,比如模塊化、松散耦合、面向接口編程、類應只關注本職工作等等開發設計理念以及規范命名、詳盡使用的注釋、清晰的結構等代碼規范以及每日構建、有效溝通、配置管理、bug管理等一些管理理念,這些做起來都非常容易,關鍵是懶惰是人的本性,不知不覺中我們就會犯著大家都在重復的錯誤。如果在項目開工之初就充分貫徹這些優秀的理念,在項目進行中無論時間多緊都持之以恒,并且項目進行中不斷的反思代碼中的壞味道,一經發現立即重構,相信我們的開發過程會進入一個良性的循環中去,我們的開發人員將會體會到什么是快樂開發。
徐辛波,西安,從事軟件開發設計工作,熟悉Java語言,愛好開發工作,特別是java相關的編程,業余關注開源項目,誠心結識志同道合之士組建開源團隊,共同學習、進步、協作、為中國開源事業貢獻微薄之力。
徐辛波 sinpo.xu@gmail.com
JDOM因其簡潔易用易懂的API而被廣泛的使用。JDOM常用的核心類及它們間的關系如下圖所示:

Document代表了文檔對象,抽象類Content表示文檔中的內容元素,各種內容組成了文檔對象。常用的內容元素有xml元素Element、xml注釋Comment、文本Text。下面以如下片段來說明各類的含義。
<?xml version="1.0" encoding="UTF-8"?>
<customers>
<customer>
<name>徐辛波</name>
<occupation>developer</occupation>
<!-- comment:following is contact info -->
<contact>
<email>sinpo.xu@hotmail.com</email>
<mobile>15029357227</mobile>
<fix-phone>02985457683</fix-phone>
</contact>
</customer>
</customers>
上述文檔用Document來抽象;customers為文檔的根元素(root element ),Element即一個封閉起來的元素,element元素可以有子元素,如<mobile>15029357227</mobile>是一個元素,而<contact>...</contact>也是一個元素,甚至<customers>...</customers>也是一個大元素;<!-- ... -->代表了xml中注釋,注釋在JDOM中用Comment類來抽象;Text代表了xml中的文本值,如元素屬性的值、元素的值、注釋的內容等,父元素的Text為子元素和值組成的串,使用Text類可以方便的表示一些特殊字符,如:
Element element = new Element("name");
Text text = new Text("AAA.<、BBB/>.<CCC>");
element.addContent(text);
值得一提的是Element的方法addContent(Content content),因參數是抽象父類Content,所以可以添加Text、Element和Comment等,如果添加的是Text則自動作為element的文本值,如果是Element則作為element的子元素,如果是Comment則作為element的注釋,使用十分方便。元素的值如<name>徐辛波</name>中的“徐辛波”也是一個和元素平行的Content對象(Text對象),當使用Element的getDescendants()方法時將返回一個該元素所有后代的迭代器,這些后代包括Element、Comment、Text等,如元素<contact>的后代包括email、mobile、fix-phone三個元素以及這三個元素的Text共6個后代,如果計算后代時有父子嵌套則應注意,父元素作為一個后代,其嵌套的子元素作為另一個后代。
剛才提到核心類都包含在org.jdom包下,jdom還包含了org.jdom.input和org.jdom.output兩個包分別來處理xml內容的輸入輸出。當要讀取xml資源時我們通常使用input包下的SAXBuilder類從輸入流構建dom對象,當資源加載后常用的做法是在內存中緩存,這樣后續的查找修改等操作就非常快。文檔加載后內存的中各個元素是記錄有各自的位置和關系的,即保持有上下文環境的。如果想要刪除一段內容(Element Comment Text),只用調用該內容的detach方法即可,這樣元素即和文檔脫離關系了,再對文檔進行遍歷或者持久化到磁盤上時游離的元素就不可見了。Jdom的輸出類包括XMLOutputter、DOMOutputter、SAXOutputter。最常用的是XMLOutputter,通過它可以將dom對象輸出到指定的輸出流,并且可以指定所輸出xml文件的格式,比如縮進的樣式等。DOMOutputter輸出org.w3c.dom.Document對象,用于JDOM對象同w3c dom對象轉換,SAXOutputter可以注冊回調函數來處理相應的sax事件。
一下示例代碼實現一個常用的讀取配置文件并且允許更改后同步到磁盤的操作:
package sinpo.usagedemo;
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.List;
import org.jdom.Document; import org.jdom.Element; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter;
/** * 讀取配置文件,并且修改后及時同步到磁盤 * @author 徐辛波(sinpo.xu@hotmail.com) * Oct 23, 2008 */ public class Configuration {
private Element root = null;
private Document dom = null;
private static final String resourceName = "/config.xml";
private static Configuration _INSTANCE = null;
public static synchronized Configuration getInstance() { if (_INSTANCE == null) { _INSTANCE = new Configuration(); }
return _INSTANCE; }
private Configuration() { load(); }
public String getConfig(String configName) { String configValue = null; Element found = findRecursively(configName, root); if (found != null) { configValue = found.getText(); } return configValue; }
public void updateConfig(String configName, String newValue) throws IOException { Element found = findRecursively(configName, root); if (found != null) { found.setText(newValue); } else { Element configNode = new Element(configName); configNode.addContent(newValue); // also: configNode.setText(newValue); root.addContent(configNode); } sync(); }
public void deleteConfig(String configName) throws IOException { Element found = findRecursively(configName, root); if (found != null) { found.detach(); } sync(); } private void load() { SAXBuilder builder = new SAXBuilder(); InputStream source = getClass().getResourceAsStream(resourceName); try { dom = builder.build(source); root = dom.getRootElement(); } catch (Exception e) { e.printStackTrace(); } }
// 遞歸查找. 在指定的父節點下查找葉子元素 private Element findRecursively(String name, Element parent) { Element found = null; List<Element> children = parent.getChildren(); if (children != null) { for (int i = 0; i < children.size(); i++) { Element element = children.get(i); String tmpName = element.getName(); if ((name.equals(tmpName)) && (!hasChild(element))) { return element; } }
for (int i = 0; i < children.size(); i++) { Element element = children.get(i); if (hasChild(element)) { found = findRecursively(name, element); if (found != null) { return found; } } } }
return found; }
private boolean hasChild(Element element) { boolean hasChild = false; List children = element.getChildren(); if ((children != null) && (children.size() > 0)) { hasChild = true; }
return hasChild; }
private void sync() throws IOException { Format format = Format.getPrettyFormat(); XMLOutputter outputter = new XMLOutputter(format); File file = null; URL url = getClass().getResource(resourceName); if (url == null) { file = new File(resourceName); } else { file = new File(url.getPath());
OutputStream out = null; try { out = new FileOutputStream(file); outputter.output(dom, out); out.close(); out = null; } catch (Exception e) { e.printStackTrace(); if (out != null) { out.close(); } } } } } |
關于線程間的交互和共享數據通常有輪詢和通知機制。一下舉例說明:Thread1和Thread2共享一塊數據ShareData,Thread1使用數據,Thread2更新數據。當Thread1使用數據時發現數據沒有更新就可以先休眠(sleep())一段時間然后再去判斷是否更新,如此反復直到數據可用,這就是所述的輪詢機制。可以看出輪詢機制需要不斷的輪詢數據狀態,很耗費資源;當采用通知機制時過程是這樣的,Thread1發現數據不可用就在ShareData上等待(ShareData.wait()),當Thread2更新數據后就通知所有在ShareData上等待的線程(ShareData.notifyAll()),這樣Thread1受到通知繼續運行。
關于等待和休眠還有另一個區別就是當線程等待時,該線程鎖定的資源是釋放掉的,這時其它線程是可以鎖定這些資源的,當線程被喚醒或者等待時限到時線程重新獲取資源才能繼續運行;而當線程休眠時線程鎖定的資源是不被釋放的。
還有一點就是要在對象lock上等待時是必須先要獲取lock的對象鎖才能進行的,即必須要類似下面的邏輯
synchronized(lock){ lock.wait()}
以下為一個簡單的示例:
package
sinpo.usagedemo;
/**
* 該例子說明線程休眠與等待以及注意事項。
*
*
@author
徐辛波(sinpo.xu@hotmail.com)
* Oct 22, 2008
*/
public class
PendingThreadDemo
{
public
Console console =
new
Console
()
;
private
void
writeToConsole1
() {
synchronized
(
console
){
try
{
Thread.sleep
(
1
*
1000
)
;
//NOTE:sleep時并未釋放console別的線程是不能鎖定console的
//TODO do things
}
catch
(
InterruptedException e
) {
e.printStackTrace
()
;
}
}
}
private
void
writeToConsole2
() {
synchronized
(
console
){
try
{
console.wait
(
1
*
1000
)
;
//NOTE:wait時別的線程是可以鎖定console的
//TODO do things
}
catch
(
InterruptedException e
) {
e.printStackTrace
()
;
}
}
}
}
//控制臺類
class
Console
{
//TODO implements me
}
|
麻煩有誰知道的告知一下。。。
今天終于找到一個不錯的工具,能將用eclipse等編輯的java代碼做成一樣風格的html格式,試用了挺好用的,謝謝
Java2Html團隊。
這幾天需要實現一個底層基于UDP的協議,該協議底層使用UDP傳輸但是具有擁塞控制、超時重發、數據確認等功能又比TCP簡單 (RUDP,Reliable UDP)。在實現協議底層的UDP服務時準備使用Java的NIO,在網上查資料都是以TCP為例講的,于是自己研究了一下基于UDP的NIO。
NIO的思路是基于多路選擇的,即由原來的每個連接都由一個線程來等待消息,改為每個連接都在選擇器上注冊,由選擇器來等待。當然NIO引入了很多新的概念,如Channel,Buffer、Charset、Selector等,使得編程更簡潔、更面向對象化。
下面貼出用NIO API改造成UDP示例代碼,注意其中使用Charset來編碼解碼的過程(當然Charset還支持很多其他編碼不僅局限于默認編碼)以及Buffer的使用。
package
sinpo.usagedemo;
import
java.net.DatagramSocket;
import
java.net.InetSocketAddress;
import
java.net.SocketAddress;
import
java.nio.ByteBuffer;
import
java.nio.CharBuffer;
import
java.nio.channels.DatagramChannel;
import
java.nio.channels.SelectionKey;
import
java.nio.channels.Selector;
import
java.nio.charset.Charset;
import
java.util.Iterator;
import
java.util.Set;
/**
*
@author
徐辛波(sinpo.xu@hotmail.com) Oct 19, 2008
*/
public class
UDPServer
extends
Thread
{
public
void
run
() {
Selector selector =
null
;
try
{
DatagramChannel channel = DatagramChannel.open
()
;
DatagramSocket socket = channel.socket
()
;
channel.configureBlocking
(
false
)
;
socket.bind
(
new
InetSocketAddress
(
5057
))
;
selector = Selector.open
()
;
channel.register
(
selector, SelectionKey.OP_READ
)
;
}
catch
(
Exception e
) {
e.printStackTrace
()
;
}
ByteBuffer byteBuffer = ByteBuffer.allocate
(
65536
)
;
while
(
true
) {
try
{
int
eventsCount = selector.select
()
;
if
(
eventsCount >
0
) {
Set selectedKeys = selector.selectedKeys
()
;
Iterator iterator = selectedKeys.iterator
()
;
while
(
iterator.hasNext
()) {
SelectionKey sk =
(
SelectionKey
)
iterator.next
()
;
iterator.remove
()
;
if
(
sk.isReadable
()) {
DatagramChannel datagramChannel =
(
DatagramChannel
)
sk
.channel
()
;
SocketAddress sa = datagramChannel
.receive
(
byteBuffer
)
;
byteBuffer.flip
()
;
// 測試:通過將收到的ByteBuffer首先通過缺省的編碼解碼成CharBuffer 再輸出
CharBuffer charBuffer = Charset.defaultCharset
()
.decode
(
byteBuffer
)
;
System.out.println
(
"receive message:"
+ charBuffer.toString
())
;
byteBuffer.clear
()
;
String echo =
"This is the reply message from 服務器。"
;
ByteBuffer buffer = Charset.defaultCharset
()
.encode
(
echo
)
;
datagramChannel.write
(
buffer
)
;
}
}
}
}
catch
(
Exception e
) {
e.printStackTrace
()
;
}
}
}
public static
void
main
(
String
[]
args
) {
new
UDPServer
()
.start
()
;
}
}
|
Client
package
sinpo.usagedemo;
import
java.net.InetSocketAddress;
import
java.net.SocketAddress;
import
java.nio.ByteBuffer;
import
java.nio.channels.DatagramChannel;
import
java.nio.channels.SelectionKey;
import
java.nio.channels.Selector;
import
java.nio.charset.Charset;
import
java.util.Iterator;
import
java.util.Set;
/**
*
@author
徐辛波(sinpo.xu@hotmail.com)
* Oct 19, 2008
*/
public class
UDPClient
extends
Thread
{
public
void
run
() {
DatagramChannel channel =
null
;
Selector selector =
null
;
try
{
channel = DatagramChannel.open
()
;
channel.configureBlocking
(
false
)
;
SocketAddress sa =
new
InetSocketAddress
(
"localhost"
,
5057
)
;
channel.connect
(
sa
)
;
}
catch
(
Exception e
) {
e.printStackTrace
()
;
}
try
{
selector = Selector.open
()
;
channel.register
(
selector, SelectionKey.OP_READ
)
;
channel.write
(
Charset.defaultCharset
()
.encode
(
"Tell me your time"
))
;
}
catch
(
Exception e
) {
e.printStackTrace
()
;
}
ByteBuffer byteBuffer = ByteBuffer.allocate
(
100
)
;
while
(
true
) {
try
{
int
eventsCount = selector.select
()
;
if
(
eventsCount >
0
) {
Set selectedKeys = selector.selectedKeys
()
;
Iterator iterator = selectedKeys.iterator
()
;
while
(
iterator.hasNext
()) {
SelectionKey sk =
(
SelectionKey
)
iterator.next
()
;
iterator.remove
()
;
if
(
sk.isReadable
()) {
DatagramChannel datagramChannel =
(
DatagramChannel
)
sk
.channel
()
;
datagramChannel.read
(
byteBuffer
)
;
byteBuffer.flip
()
;
//TODO 將報文轉化為RUDP消息并調用RUDP協議處理器來處理
System.out.println
(
Charset.defaultCharset
()
.decode
(
byteBuffer
)
.toString
())
;
byteBuffer.clear
()
;
datagramChannel.write
(
Charset.defaultCharset
()
.encode
(
"Tell me your time"
))
;
}
}
}
}
catch
(
Exception e
) {
e.printStackTrace
()
;
}
}
}
}
|