blogjava-凯发k8网页登录http://blogjava.net/dlevin/category/48883.htmlin general the oo style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. to do is to be -nietzsche, to bei is to do -kant, do be do be do -sinatrazh-cnthu, 13 aug 2015 10:48:20 gmtthu, 13 aug 2015 10:48:20 gmt60分布式map中实现引用计数http://www.blogjava.net/dlevin/archive/2015/04/20/424554.htmldlevindlevinmon, 20 apr 2015 12:30:00 gmthttp://www.blogjava.net/dlevin/archive/2015/04/20/424554.htmlhttp://www.blogjava.net/dlevin/comments/424554.htmlhttp://www.blogjava.net/dlevin/archive/2015/04/20/424554.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/424554.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/424554.html前言在《》中,详细介绍了如何在一个进程中实现一个无锁版本的referencecountset(或者说是在自己的代码里没有锁),但是最近遇到一个问题,如果是在分布式的环境中呢?如何实现这个引用计数?这个问题如果从头开始写,会是一个比较复杂的问题,在实际中,我们可以使用zookeeper设置时的version机制来实现,即cas(compare-and-set)。这是一个本人在实际项目中遇到的一个问题,但是会更简单一些,因为在我们的项目中,我们使用gemfire,即已经有一个现成的分布式map了(在gemfire中叫做region),所以问题简化成如果如何使用一个分布式map实现引用计数?

blogjava-凯发k8网页登录

如果对concurrentmap接口比较熟悉的话,这个其实是一个比较简单的问题。在concurrentmap中最主要的就是引入几个cas相关的操作:
public interface concurrentmap extends map {
    v putifabsent(k key, v value);
    boolean remove(object key, object value);
    boolean replace(k key, v oldvalue, v newvalue);
    v replace(k key, v value);
}
在《》中我们只需要使用putifabsent就可以了,剩下的实现可以交给atomicinteger提供的cas来实现,因为它是在同一个进程中,但是如果在分布式的环境中就不能使用这个atomicinteger,这个时候应该怎么办呢?其实这个时候我们就可以求助于replace方法了。replace方法的注释中这样描述:
    /**
     * replaces the entry for a key only if currently mapped to a given value.
     * this is equivalent to
     * 

     *   if (map.containskey(key) && map.get(key).equals(oldvalue)) {
     *       map.put(key, newvalue);
     *       return true;
     *   } else return false;

     * except that the action is performed atomically.
     *
     * 
@param key key with which the specified value is associated
     * 
@param oldvalue value expected to be associated with the specified key
     * 
@param newvalue value to be associated with the specified key
     * 
@return true if the value was replaced
     * 
@throws unsupportedoperationexception if the put operation
     *         is not supported by this map
     * 
@throws classcastexception if the class of a specified key or value
     *         prevents it from being stored in this map
     * 
@throws nullpointerexception if a specified key or value is null,
     *         and this map does not permit null keys or values
     * 
@throws illegalargumentexception if some property of a specified key
     *         or value prevents it from being stored in this map
     
*/
    boolean replace(k key, v oldvalue, v newvalue);

在concurrentmap的value中我们只需要给integer,然后用replace去不断的尝试,即自己实现一个cas:
private int incrementrefcount(object key) {
    do {
        integer curcount = distributedmap.get(key);
        if (curcount == null) {
            curcount = distributedmap.putifabsent(key, new integer(1));
            if (curcount == null) {
                return 1;
            }
        }
        
        integer newcount = new integer(curcount.intvalue()   1);
        if (distributedmap.replace(key, curcount, newcount)) {
            return newcount;
        }
    } while (true);
}

主要逻辑就是这样了,其实比较简单,只是之前没有遇到过这个问题,所以感觉可以记录下来。或许什么时候补充一下zookeeper版本的实现。

dlevin 2015-04-20 20:30
]]>
referencecountset无锁实现http://www.blogjava.net/dlevin/archive/2014/12/06/421110.htmldlevindlevinfri, 05 dec 2014 16:29:00 gmthttp://www.blogjava.net/dlevin/archive/2014/12/06/421110.htmlhttp://www.blogjava.net/dlevin/comments/421110.htmlhttp://www.blogjava.net/dlevin/archive/2014/12/06/421110.html#feedback3http://www.blogjava.net/dlevin/comments/commentrss/421110.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/421110.html阅读全文

dlevin 2014-12-06 00:29
]]>
使用exchanger实现两个线程之间的数据交互http://www.blogjava.net/dlevin/archive/2014/03/23/411377.htmldlevindlevinsun, 23 mar 2014 05:40:00 gmthttp://www.blogjava.net/dlevin/archive/2014/03/23/411377.htmlhttp://www.blogjava.net/dlevin/comments/411377.htmlhttp://www.blogjava.net/dlevin/archive/2014/03/23/411377.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/411377.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/411377.html 1. 建立一个连接(创建serversocket实例,一般还会给定一个端口,其实可以bind(null)以让操作系统分配一个可用端口),新启动一个线程,在新线程中监听给定端口(调用accept方法)。
2. 发送客户端请求(创建一个socket实例,并向该socket写入请求数据)。
3. 在接收端读取数据,验证写入的请求和接收到的数据相同。

在以上流程实现中,accept方法返回的接收端socket需要传给主线程,同时要保证使用该socket是在accept方法返回之后,以我习惯,我会使用一个lock或countdownlatch:
private static class socketholder {
    socket socket;
}

@test
public void levinoldwaytest() throws exception {
    final serversocket server = new serversocket(10240);
    
    final countdownlatch latch = new countdownlatch(1);
    final socketholder socketholder = new socketholder();
    new thread() {
        public void run() {
            try {
                socketholder.socket = server.accept();
                latch.countdown();
            } catch(exception ex) {
                ex.printstacktrace();
            }
        }
    }.start();
    
    socket socket = new socket(server.getinetaddress(), server.getlocalport());
    socket.getoutputstream().write("my test string".getbytes());
    
    latch.await(5, timeunit.seconds);
    byte[] receives = new byte[4096];
    int length = socketholder.socket.getinputstream().read(receives);
    
    assertequals("my test string", new string(receives, 0, length));
    
    socket.close();
    socketholder.socket.close();
    server.close();
}

不知道有多少人也像我一样把这段代码写成这样?这里有两个问题:
1. serversocket的监听的端口不一定是可用的,类似测试代码我之前没有写过,我估计自己正真在写的时候应该会想到让操作系统动态分配。
2. 为了在两个线程中传递数据,这里首先创建了一个socketholder类,然后使用countdownlatch,写起来好麻烦。为了简化这段代码,可以使用exchanger,即当一个生产者线程准备好数据后可以通过exchanger将数据传递给消费者,而消费者在生产者传递过来数据后就可以消费了,这里的数据就是socket。

改进后的代码如下:
@test
public void levinimprovedwaytest() throws exception {
    final serversocket server = new serversocket();
    server.bind(null);
    
    final exchanger exchanger = new exchanger();
    new thread() {
        public void run() {
            try {
                exchanger.exchange(server.accept());
            } catch(exception ex) {
                ex.printstacktrace();
            }
        }
    }.start();
    
    socket socket = new socket(server.getinetaddress(), server.getlocalport());
    socket.getoutputstream().write("my test string".getbytes());
    
    socket receiversocket = exchanger.exchange(null, 5, timeunit.seconds);
    byte[] receives = new byte[4096];
    int length = receiversocket.getinputstream().read(receives);
    
    assertequals("my test string", new string(receives, 0, length));
    
    socket.close();
    receiversocket.close();
    server.close();
}


dlevin 2014-03-23 13:40
]]>
java core系列之treemap实现详解http://www.blogjava.net/dlevin/archive/2013/10/26/405667.htmldlevindlevinsat, 26 oct 2013 10:41:00 gmthttp://www.blogjava.net/dlevin/archive/2013/10/26/405667.htmlhttp://www.blogjava.net/dlevin/comments/405667.htmlhttp://www.blogjava.net/dlevin/archive/2013/10/26/405667.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/405667.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/405667.html
红黑树引入的目的
首先要从对有序序列的查找问题开始,对一个静态的有序序列数组,只需要二分查找即可得到o(log(n))的查找效率;然而实现并不会显得那么“静态”,需要实现动态的插入、删除同时保持序列的有序性,并且尽量提高插入、删除、查找的效率。

为实现动态插入、删除,最简单的实现是二叉排序树(bst, binary sort tree),它具有以下性质:
1. 它可以是一个空树。
2. 若它的左子树不为空,则它的左子树所有节点的值均小于根节点的值。
3. 若它的右子树不为空,则它的右子树所有节点的值均大于根节点的值。
4. 它的左子树和右子树都是一个二叉排序树。
根据以上性质,
对查找,比较查找数和当前节点,如果查找数和当前节点相等,则找到返回;如果查找数小于当前节点,查找其左子树,如果查找数大于当前节点,查找其右子树,直到找到或直到叶子节点为null,返回null。
对插入,先查找这棵树,如果找到已存在的节点,更新节点值,否则把新值插入到最后一个为null的节点中。
对删除,首先找到要删除的节点,a)如果找到的节点p没有子节点,直接删除即可;b)如果找到的节点p只有左子树或右子树,删除该节点,并将其父节点原来的指针指向它唯一的左子树、或右子树即可;c)如果找到的节点p既有左子树又有右子树,可以有两种做法:i)删除节点p,把节点p的父节点原来指向p的指针指向节点p的左字节点,而将节点p的右节点插入到节点p右节点的最右叶子节点上(如果节点p是其父节点的右节点,则将节点p的父节点原来指向p节点的指针指向p节点的右子树,而将节点p的左子树插入到节点p最左叶子节点上),ii)将节点p替换成节点p的直接前驱(或直接后继),然后删除节点p的直接前驱(或直接后继)(注:直接前驱查找:节点p左子树的最右节点,直接后继查找:节点p右子树的最左节点)。
二叉排序树实现比较简单,但是它查找效率却不理想,最好的效率是o(log(n)),最会效率为o(n)。

为了提高查找效率,后来有人提出了平衡二叉树(avl树),它具有二叉排序树的所有特点,并添加了以下性质:
1. 左子树和右子树的深度之差绝对值不超过1。
2. 左子树和右子树都是平衡二叉树。
为了实现平衡二叉树,需要在没个节点上添加平衡因子字段,在插入后,如果发现平衡因子不符合性质1,则需要经过旋转以调整。平衡二叉树可以保证其查找的最好、最坏查找时间复杂度都为o(log(n))。

红黑树是平衡二叉树的变种,它是一种自平衡的二叉排序树,也需要通过一些旋转调整以保持它的性质,它的名字是在 leo j. guibas 和 robert sedgewick 于1978年写的一篇论文中获得的。不同于平衡二叉树的高度平衡,红黑树只能保证从根到叶子节点的最长路径不超过最短路径的2倍,因而它的最坏查找时间复杂度为o(2log(n 1)),然而有研究表明它的统计性能要好于平衡二叉树,因而它具有更广泛的应用。

红黑树的性质
一棵树需要满足以下性质才能保证它是一颗红黑树:
1. 它是一颗二叉查找树(bst)。
2. 它的所有节点是红色或黑色。
3. 它的根节点是黑色。
4. 它的叶子节点是黑色(空节点视为叶子节点)。
5. 它的红节点的子节点必须是黑色的;而黑节点的字节点可以是黑色或红色。
6. 它任一节点到其后代的叶子节点路径上有相同的黑色节点数。
一般的文章都是把性质列出来,然后根据这些性质来写代码实现(我也一样:)),但是如何得出这些性质呢?多问几个为什么总是好事,这个问题需要去读上面提到的论文,我没读过也不打算读,这貌似不是我能涉及的,那就提出问题不回答了。。。

treemap中红黑树的节点
对一颗树的节点,最基础是该节点的值、左节点指针、右节点指针。对treemap,因为它存储的是键值对,因而它包含了key、value,为了纪录节点的颜色,它还需要有color字段:
    private static final boolean red   = false;
    private static final boolean black = true;

    static final class entry implements map.entry {
        k key;
        v value;
        entry left = null;
        entry right = null;
        entry parent;
        boolean color = black;
        ....
    }

treemap中红黑树节点插入
红黑数的插入分以下步骤:
1. 如果当前为空树,插入节点直接作为根节点,并将该节点颜色比较
2. 以二叉排序树的查找算法查找当前树,如果在当前树中找到已存在的节点,更新节点的值,并返回。
3. 否则,创建一个新节点,将其插入到最后一个查找到的叶子节点上,其初始颜色为红色。
4. 如果新插入节点的父节点是黑节点,则没有破坏当前红黑树的性质,直接返回。
5. 如果新插入节点的父节点是红节点,则需要做一些调整。
在treemap中,key值的比较可以通过构造treemap传入的comparator实例,如果没有comparator,则key必须实现comparable接口作为比较逻辑,key不可以为null。以二叉排序树的算法插入新节点的代码比较简单:
    public v put(k key, v value) {
        entry t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check
            root = new entry<>(key, value, null);
            size = 1;
            modcount ;
            return null;
        }
        int cmp;
        entry parent;
        // split comparator and comparable paths
        comparatorsuper k> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setvalue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new nullpointerexception();
            comparablesuper k> k = (comparablesuper k>) key;
            do {
                parent = t;
                cmp = k.compareto(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setvalue(value);
            } while (t != null);
        }
        entry e = new entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixafterinsertion(e);
        size ;
        modcount ;
        return null;
    }
    private void fixafterinsertion(entry x) {
        x.color = red;
        ..
        root.color = black;
    }

treemap中红黑树新节点插入后调整

红黑树的调整比较复杂,首先它会从当前节点向上查找,直到当前节点为null,或是root节点,或者当前节点的父节点颜色不是红色,然后根据以下不同情况做处理(设当前节点为c(红色),其父节点为p(红色),其祖先节点为a(黑色),其叔叔节点为u(待定)):
1. p是a的左子树,u节点颜色为红色,此时不管c是节点是p的左子树还是右子树,只需要将p和u设为黑色,a设为红色,则可保证当前局部树符合红黑树定义,把a作为新插入节点重新调整,如果当前树已经是整棵树,则因为根节点为红色,不符合红黑树定义,此时只需要将根节点颜色设置为黑色即可,即fixafterinsertion()最后一句代码的作用。
2. p是a的左子树,u节点颜色为黑色,c是p的左子树,将p设置为黑色,a设置为红色,并对a做右旋操作。此时c的父节点已变为黑色,循环可以直接退出。
3. p是a的左子树,u节点颜色为黑色,c是p的右子树,此时只需要先对p左旋,然后设置c为黑色,a为红色,并对a右旋,此时p的父节点已变为黑色,循环可以直接退出。
如下图所示:

代码:
        while (x != null && x != root && x.parent.color == red) {
            if (parentof(x) == leftof(parentof(parentof(x)))) {
                entry y = rightof(parentof(parentof(x)));
                if (colorof(y) == red) {
                    setcolor(parentof(x), black);
                    setcolor(y, black);
                    setcolor(parentof(parentof(x)), red);
                    x = parentof(parentof(x));
                } else {
                    if (x == rightof(parentof(x))) {
                        x = parentof(x);
                        rotateleft(x);
                    }
                    setcolor(parentof(x), black);
                    setcolor(parentof(parentof(x)), red);
                    rotateright(parentof(parentof(x)));
                }
            } else {
                ....
            }
        }
4. p是a的右子树,u节点颜色为红色,此时不管c是节点是p的左子树还是右子树,只需要将p和u设为黑色,a设为红色,则可保证当前局部树符合红黑树定义,把a作为新插入节点重新调整,如果当前树已经是整棵树,则因为根节点为红色,不符合红黑树定义,此时只需要将根节点颜色设置为黑色即可,即fixafterinsertion()最后一句代码的作用。
5. p是a的右子树,u节点颜色为黑色,c是p的右子树,将p设置为黑色,a设置为红色,并对a做左旋操作。此时c的父节点以变为黑色,循环可以直接退出。
6. p是a的右子树,u节点颜色为黑色,c是p的左子树,此时只需要先对p左旋,然后设置c为黑色,a为红色,并对a右旋,此时p的父节点已为黑色,循环可以直接退出。
如下图所示:

代码:
        while (x != null && x != root && x.parent.color == red) {
            if (parentof(x) == leftof(parentof(parentof(x)))) {
                ....
            } else {
                entry y = leftof(parentof(parentof(x)));
                if (colorof(y) == red) {
                    setcolor(parentof(x), black);
                    setcolor(y, black);
                    setcolor(parentof(parentof(x)), red);
                    x = parentof(parentof(x));
                } else {
                    if (x == leftof(parentof(x))) {
                        x = parentof(x);
                        rotateright(x);
                    }
                    setcolor(parentof(x), black);
                    setcolor(parentof(parentof(x)), red);
                    rotateleft(parentof(parentof(x)));
                }
            }
        }

treemap中红黑树节点删除
红黑树的删除类似二叉查找树删除逻辑类似,在对同时有左子树和右子树存在时,treemap选择先将要删除的节点替换成其直接后继节点,然后删除其直接后继节点(其直接后继节点不可能同时存在左子节点和右子字节点)。对红黑树,由于红色节点不影响路径计算(性质6),因而对红色节点可以直接删除。然而在删除黑色节点时,如果删除的节点不是树的唯一节点,那么在某些路径上的黑色节点数会发生改变,破坏性质6;如果被删除的唯一子节点为红色,而父节点也为红色,那么性质5被破坏,因为存在红色节点的子节点为红色;如果删除的是根节点,而它的唯一子节点是红色,则性质3被破坏。因而需要做一些调整。
    private void deleteentry(entry p) {
        modcount ;
        size--;

        // if strictly internal, copy successor's element to p and then make p
        
// point to successor.
        if (p.left != null && p.right != null) {
            entry s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        
// start fixup at replacement node, if it exists.
        entry replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // null out links so they are ok to use by fixafterdeletion.
            p.left = p.right = p.parent = null;

            // fix replacement
            if (p.color == black)
                fixafterdeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  no children. use self as phantom replacement and unlink.
            if (p.color == black)
                fixafterdeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

treemap中红黑树删除黑色节点后调整
调整的逻辑分以下步骤来考虑(假设新替换的节点为c,即代码中的x参数,c的父节点为p,c是p的左子节点,c的兄弟节点为s,s的左子树为sl,s的右子树为sr):
1. 如果c为红色,直接将c标记为黑色即可,因为删除的黑节点数被该节点补上,该树已经恢复成一颗红黑树。
2. 如果c为黑色,且c为根节点,直接返回。
3. 如果c为黑色,且s为红色,那么节点p、sl、sr都为黑色,此时设置p为红色,s为黑色,对p左旋,并重新计算s,即s变为sl,即把问题转换为兄弟节点为黑色的情况。图片来自http://blog.csdn.net/v_july_v/article/details/6105630,自己画太麻烦了,虽然图片的命名规则和我的不一样,凑合的看把,囧。
 
4. 如果c为黑色,s为黑色,且sl、sr都为黑色,将s设置为红色,p赋值给c,重新计算。

5. 如果c为黑色,s为黑色,且sl为红色,sr为黑色,那么设置sl为黑色,s为红色,对s右旋,重新设置s为sl。

6. 如果c为黑色,s为黑色,且sr为红色,sl为任一颜色,则把s节点的颜色设置为p节点的颜色,设置p节点的颜色为黑色,sr节点的颜色为黑色,对p节点右旋,算法结束。
 
当c为p的右子节点时,其逻辑和以上对称,不再赘述。
    private void fixafterdeletion(entry x) {
        while (x != root && colorof(x) == black) {
            if (x == leftof(parentof(x))) {
                entry sib = rightof(parentof(x));

                if (colorof(sib) == red) {
                    setcolor(sib, black);
                    setcolor(parentof(x), red);
                    rotateleft(parentof(x));
                    sib = rightof(parentof(x));
                }

                if (colorof(leftof(sib))  == black &&
                    colorof(rightof(sib)) == black) {
                    setcolor(sib, red);
                    x = parentof(x);
                } else {
                    if (colorof(rightof(sib)) == black) {
                        setcolor(leftof(sib), black);
                        setcolor(sib, red);
                        rotateright(sib);
                        sib = rightof(parentof(x));
                    }
                    setcolor(sib, colorof(parentof(x)));
                    setcolor(parentof(x), black);
                    setcolor(rightof(sib), black);
                    rotateleft(parentof(x));
                    x = root;
                }
            } else { // symmetric
                entry sib = leftof(parentof(x));

                if (colorof(sib) == red) {
                    setcolor(sib, black);
                    setcolor(parentof(x), red);
                    rotateright(parentof(x));
                    sib = leftof(parentof(x));
                }

                if (colorof(rightof(sib)) == black &&
                    colorof(leftof(sib)) == black) {
                    setcolor(sib, red);
                    x = parentof(x);
                } else {
                    if (colorof(leftof(sib)) == black) {
                        setcolor(rightof(sib), black);
                        setcolor(sib, red);
                        rotateleft(sib);
                        sib = leftof(parentof(x));
                    }
                    setcolor(sib, colorof(parentof(x)));
                    setcolor(parentof(x), black);
                    setcolor(leftof(sib), black);
                    rotateright(parentof(x));
                    x = root;
                }
            }
        }

        setcolor(x, black);
    }

treemap中红黑树节点查找
红黑树的节点查找同二叉查找树逻辑,不再赘述。这里有一点不太明白:getentryusingcomparator()方法注释中说它从getentry()方法提取出来是为了性能上的考虑,这是为什么?
    /**
     * version of getentry using comparator. split off from getentry
     * for performance. (this is not worth doing for most methods,
     * that are less dependent on comparator performance, but is
     * worthwhile here.)
     
*/
    final entry getentryusingcomparator(object key)

treemap中其他方法
treemap中其他方法实现比较直观,只要理解了红黑树,基本上很容易理解,不再赘述。

参考链接:
http://blog.csdn.net/v_july_v/article/details/6105630
http://blog.csdn.net/zhaojinjia/article/details/8120403
http://blog.csdn.net/eric491179912/article/details/6179908
http://dongxicheng.org/structure/red-black-tree/


dlevin 2013-10-26 18:41
]]>
java core系列之concurrenthashmap实现(jdk 1.7)http://www.blogjava.net/dlevin/archive/2013/10/18/405030.htmldlevindlevinfri, 18 oct 2013 14:24:00 gmthttp://www.blogjava.net/dlevin/archive/2013/10/18/405030.htmlhttp://www.blogjava.net/dlevin/comments/405030.htmlhttp://www.blogjava.net/dlevin/archive/2013/10/18/405030.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/405030.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/405030.html阅读全文

dlevin 2013-10-18 22:24
]]>
java core系列之hashmap实现http://www.blogjava.net/dlevin/archive/2013/10/15/404984.htmldlevindlevintue, 15 oct 2013 15:40:00 gmthttp://www.blogjava.net/dlevin/archive/2013/10/15/404984.htmlhttp://www.blogjava.net/dlevin/comments/404984.htmlhttp://www.blogjava.net/dlevin/archive/2013/10/15/404984.html#feedback2http://www.blogjava.net/dlevin/comments/commentrss/404984.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/404984.html
map是对键值对存储的抽象,因而其最主要的方法有:
1. 添加新的键值对(key,value);
2. 通过键(key)查找关联的值(value);
3. 通过键(key)移除关联的值(value);
4. 判断键(key)或值(value)的存在性。
其他的方法有:判断键值对的空属性以及目前的键值对数,获取所有键、所有值或者所有键值对的集合,批量添加,清除所有键值对等。在map中,一个键值对用entry接口来表示。因而在java中,对map接口的定义如下:
public interface map {
    boolean isempty();
    boolean containskey(object key);
    boolean containsvalue(object value);
    v get(object key);
    v put(k key, v value);
    v remove(object key);
    void putall(mapextends k, ? extends v> m);
    void clear();
    set keyset();
    collection values();
    set> entryset();

    interface entry {
        k getkey();
        v getvalue();
        v setvalue(v value);
        boolean equals(object o);
        int hashcode();
    }

    boolean equals(object o);
    int hashcode();
}
hashmap是哈希表对map非线程安全版本的实现,它允许key为null,也允许value为null。所谓哈希表就是通过一个哈希函数计算出一个key的哈希值,然后使用该哈希值定位对应的value所在的位置;如果出现哈希值冲突(多个key产生相同的哈希值),则采用一定的冲突处理方法定位到正真value位置,然后返回查找到的value值。一般哈希表内部使用一个数组实现,使用哈希函数计算出key对应数组中的位置,然后使用处理冲突法找到真正的value,并返回。因而实现哈希表最主要的问题在于选择哈希函数和冲突处理方法,好的哈希函数能使数据分布更加零散,从而减少冲突的可能性,而好的冲突处理方法能使冲突处理更快,尽量让数据分布更加零散,从而不会影响将来的冲突处理方法。

在严蔚敏、吴伟明版本的《数据结构(c语言版)》中提供的哈希函数有:1. 直接定址法(线性函数法);2. 数字分析法;3. 平方取中法;4. 折叠法;5. 除留余数法;6. 随机数法。在jdk的hashmap中采用了移位异或法后除留余数(和2的n次方'&'操作)。hashmap内部的数据结构是一个entry的数组,在使用key查找value时,先使用key实例计算hash值,然后对计算出的hash值做各种移位和异或操作,然后取其数组的最大索引值的余数('&'操作,一般其容量值都是2的倍数,因而可以认为是除留余数)。在jdk 1.7中对string类型采用了内部hash算法(当数组容量超过一定的阀值,使用“jdk.map.althashing.threshold”设置该阀值,默认为integer.max_value,即关闭该功能),并且使用了一个hashseed作为初始值,不了解这些算法的具体缘由,就这样浅尝辄止了。
    final int hash(object k) {
        int h = 0;
        if (usealthashing) {
            if (k instanceof string) {
                return sun.misc.hashing.stringhash32((string) k);
            }
            h = hashseed;
        }
        h ^= k.hashcode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    static int indexfor(int h, int length) {
        return h & (length-1);
    }
同样在上述的数据结构书中关于冲突处理提供了几个方法:1. 开放定址法;2. 再哈希法;3.链地址法;4. 建立一个公共溢出区法。在jdk的hashmap中采用了链地址法,即每个数组bucket中存放的是一个entry链,每次新添加一个键值对,就是向链头添加一个entry实例,新添加的entry的下一个元素是原有的链头(如果该数组bucket不存在entry链,则原有链头值为null,不影响逻辑)。每个entry包含key、value、hash值和指向下一个entry的next指针。
    static class entry implements map.entry {
        final k key;
        v value;
        entry next;
        int hash;
    }

添加
从以上描述中,我们可以知道添加新的键值对可以分成两部分:
1. 使用key计算出内部数组的索引值(index)。
2. 如果该索引的数组bucket中已经存在entry链,并且该链中已经存在新添加的key的值,则将原有的值设置成新添加的值,并返回旧值。
3. 否则,创建新的entry实例,将该实例插入到原有链的头部。
4. 在新添加entry实例时,如果当前size超过阀值(capacity * loadfactor),数组容量将会自动扩大两倍,在数组扩容时,所有原存在的entry会重新计算索引值,并且entry链的顺序也会发生颠倒(如果还在同一个链中的话);而该新添加的entry的索引值也会重新计算。
5. 对key为null时,默认数组的索引值为0,其他逻辑不变。
    void addentry(int hash, k key, v value, int bucketindex) {
        if ((size >= threshold) && (null != table[bucketindex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketindex = indexfor(hash, table.length);
        }
        createentry(hash, key, value, bucketindex);
    }
    void createentry(int hash, k key, v value, int bucketindex) {
        entry e = table[bucketindex];
        table[bucketindex] = new entry<>(hash, key, value, e);
        size ;
    }
插入原理图:

查找
查找和添加类似,首先根据key计算出数组的索引值(如果key为null,则索引值为0),然后顺序查找该索引值对应的entry链,如果在entry链中找到相等的key,则表示找到相应的entry记录,否则,表示没找到,返回null。对get()操作返回entry中的value值,对于containskey()操作,则判断是否存在记录,两个方法都调用getentry()方法:
    final entry getentry(object key) {
        int hash = (key == null) ? 0 : hash(key);
        for (entry e = table[indexfor(hash, table.length)]; e != null; e = e.next) {
            object k;
            if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
而对于value查找(如containsvalue()操作)则需要整个表遍历(数组遍历和数组中的entry链遍历),因而这种查找的效率比较低,代码实现也比较简单。

移除
移除操作(remove())也是先通过key值计算数组中的索引号(当key为null时索引号为0),从而找到entry链,查找entry链中的entry,并将该entry删除。

遍历
hashmap中实现了一个hashiterator,它首先遍历数组,查找到一个非null的entry实例,记录该entry所在数组索引,然后在下一个next()操作中,继续查找下一个非null的entry,并将之前查找到的非null entry返回。为实现遍历时不能修改hashmap的内容(可以更新已存在的记录的值,但是不可以添加、删除原有记录),hashmap记录一个modcount字段,在每次添加或删除操作起效时,将modcount自增,而在创建hashiterator时记录当前的modcount值(expectedmodcount),如果在遍历过程中(next()、remove())操作时,hashmap中的modcount和已存储的expectedmodcount不一样,表示hashmap已经被修改,抛出concurrentmodificationexception。即所谓的fail fast原则。
在hashmap中返回的key、value、entry集合都是基于该iterator实现,实现比较简单,不细讲。

注:clear()操作引起的内存问题-由于clear()操作只是将数组中的所有项置为null,数组本身大小并不改变,因而当某个hashmap已存储过较大的数据时,调用clear()有些时候不是一个好的做法。

最后吐槽一下,即使jdk内部的hashmap也有很多的代码重复。。。。。

dlevin 2013-10-15 23:40
]]>
sax解析xml文件http://www.blogjava.net/dlevin/archive/2012/11/18/391545.htmldlevindlevinsun, 18 nov 2012 12:10:00 gmthttp://www.blogjava.net/dlevin/archive/2012/11/18/391545.htmlhttp://www.blogjava.net/dlevin/comments/391545.htmlhttp://www.blogjava.net/dlevin/archive/2012/11/18/391545.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/391545.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/391545.html阅读全文

dlevin 2012-11-18 20:10
]]>
【转】关于accesscontroller.doprivilegedhttp://www.blogjava.net/dlevin/archive/2012/11/02/390637.htmldlevindlevinthu, 01 nov 2012 17:04:00 gmthttp://www.blogjava.net/dlevin/archive/2012/11/02/390637.htmlhttp://www.blogjava.net/dlevin/comments/390637.htmlhttp://www.blogjava.net/dlevin/archive/2012/11/02/390637.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/390637.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/390637.htmlhttp://hi.baidu.com/qmiao128/item/eedc21dfa60d2051d63aae2b
最近在看一些框架代码,偶尔都会遇到accesscontroller.doprivileged()方法的使用,虽然之前在《深入解析java虚拟机》这本书上看到过对这个方法的解释,但是貌似当时没有真正理解,虽然到现在对这个方法的概念还比较模糊,感觉这边文章介绍的挺清晰的,因而转载过来。

accesscontroller.doprivileged是一个在accesscontroller类中的静态方法,允许在一个类实例中的代码通知这个accesscontroller:它的代码主体是享受"privileged(特权的)",它单独负责对它的可得的资源的访问请求,而不管这个请求是由什么代码所引发的。

这就是说,一个调用者在调用doprivileged方法时,可被标识为 "特权"。在做访问控制决策时,如果checkpermission方法遇到一个通过doprivileged调用而被表示为 "特权"的调用者,并且没有上下文自变量,checkpermission方法则将终止检查。如果那个调用者的域具有特定的许可,则不做进一步检查,checkpermission安静地返回,表示那个访问请求是被允许的;如果那个域没有特定的许可,则象通常一样,一个异常被抛出。

一、"特权"特性的正常使用如下所示:

1、如果你不需要从"特权"块内返回一个值,按下列代码去做:

somemethod() {

      ...normal code here...

      accesscontroller.doprivileged(new privilegedaction() {
             public object run() {
                   // privileged code goes here, for example:
                    system.loadlibrary("awt");
                    returnnull; // nothing to return
            }
      });

      ...normal code here...

}


privilegedaction是一个接口,它带有一个被称为run的方法,这个方法返回一个object。上述例子显示了一个用来实现那个接口的匿名内类的创建,并提供了一个run方法的具体实现。

当做一个doprivileged调用时,一个privilegedaction实现的实例被传递给它。doprivileged方法在使特权生效后,从privilegedaction实现中调用run方法,并返回run方法的返回值以作为doprivileged的返回值,这一点在本例中被忽略。

2、如果你需要返回一个值,你可按如下方法去做:

somemethod() {

        ...normal code here...

        string user = (string) accesscontroller.doprivileged(new privilegedaction() {
                  public object run() {
                         return system.getproperty("user.name");
                 }
        });

        ...normal code here...

}


3、如果用你的run方法执行的动作可能扔出一个"检查"的异常(包括在一个方法的throws子句列表中),则你需要使用privilegedexceptionaction接口,而不是使用privilegedaction接口:

somemethod() throws filenotfoundexception {

        ...normal code here...

        try {
               fileinputstream fis = (fileinputstream)
               accesscontroller.doprivileged(new privilegedexceptionaction() {
                     public object run() throws filenotfoundexception {
                            returnnew fileinputstream("somefile");
                     }
               });
        } catch (privilegedactionexception e) {
                  // e.getexception() should be an instance of
                 // filenotfoundexception,
                 // as only "checked" exceptions will be "wrapped" in a
                 // privilegedactionexception.
                throw (filenotfoundexception) e.getexception();
        }

       ...normal code here...

}

有关被授予特权的一些重要事项:
首先,这个概念仅存在于一个单独线程内。一旦特权代码完成了任务,特权将被保证清除或作废。

第二,在这个例子中,在run方法中的代码体被授予了特权。然而,如果它调用无特权的不可信代码,则那个代码将不会获得任何特权;只有在特权代码具有许可并且在直到checkpermission调用的调用链中的所有随后的调用者也具有许可时, 一个许可才能被准予。

二、使用事例:

final string name = myclass.class.getname();
string classname = accesscontroller.doprivileged(new privilegedaction() {
                public string run() {
                    return system.getproperty(name);
                 }
});关于accesscontroller.doprivileged- -

                                      

来自不同的位置的代码可以由一个codesource对象描述其位置和签名证书。根据代码的codesource的不同,代码拥有不同的权限。例如所有java sdk自带的代码都具有所有的权限,而applet中的代码则具有非常受限的权限,用户编写的代码可以自己定制权限(通过securitymanager)。
当执行一段代码时,这段代码的stacktrace包含了从main开始所有正在被调用而且没有结束的方法。在这个调用过程中,很有可能出现跨多个不同的codesource的调用序列。由于codesource不同,这些代码通常拥有不同的权限集。只有所有途经的codesource都具有对应的权限集合时,当前正在运行的代码才能存取某个resource。
而doprivileged方法是对这个规则的一种补充。他类似于unix中的setuid程序。unix中的login程序必须访问password文件从而获得用户授权信息,但是用户不能随意的访问password文件。因此,login程序具有setuid位,它不管被哪个用户所调用,都具有root的权限。
调用doprivileged的方法不管其stacktrace中其他方法的权限,而仅仅根据当前方法的权限来判断用户是否能访问某个resource。也即可以规定用户只能用某种预定的方式来访问其本来不能访问的resource。
使用doprivileged方法和使用setuid位都有需要注意的地方,例如仅执行必要的操作。否则,可能带来安全上的问题。



dlevin 2012-11-02 01:04
]]>
spring中集合定义http://www.blogjava.net/dlevin/archive/2012/02/14/369971.htmldlevindlevintue, 14 feb 2012 10:16:00 gmthttp://www.blogjava.net/dlevin/archive/2012/02/14/369971.htmlhttp://www.blogjava.net/dlevin/comments/369971.htmlhttp://www.blogjava.net/dlevin/archive/2012/02/14/369971.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/369971.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/369971.html习惯了把集合定义在一个类的字段中,今天遇到想重用集合的定义,竟然不知道单独的集合bean应该怎么定义了,记之,以备后用。

对map来说,有一种比较搓的方法,就是直接用map的构造函数:
 1<bean id="symbolmap" class="java.util.hashmap">  
 2    <constructor-arg>  
 3       <map>  
 4         <entry>  
 5            <key><value>[cdata[us;djia]]>value>key>  
 6            <value>[cdata[us&dji]]>value>  
 7         entry>  
 8       map>  
 9    constructor-arg>  
10bean>  
11

另一种稍微简单的方法:

 1<bean id="emails" class="org.springframework.beans.factory.config.mapfactorybean">  
 2  <property name="sourcemap">  
 3      <map>  
 4        <entry key="pechorin" value="pechorin@hero.org"/>  
 5        <entry key="raskolnikov" value="raskolnikov@slums.org"/>  
 6        <entry key="stavrogin" value="stavrogin@gov.org"/>  
 7        <entry key="porfiry" value="porfiry@gov.org"/>  
 8      map>  
 9  property>  
10bean>  
11

对这种方法,sping还提供了listfactorybean, setfactorybean等类,这貌似是spring的一个可扩展框架,可以待以后进一步研究这个框架的实现方式。
最简单的一种方式就是直接用spring中提供的util包:

1<util:map id="emails">  
2    <entry key="pechorin" value="pechorin@hero.org"/>  
3    <entry key="raskolnikov" value="raskolnikov@slums.org"/>  
4    <entry key="stavrogin" value="stavrogin@gov.org"/>  
5    <entry key="porfiry" value="porfiry@gov.org"/>  
6util:map>  
7

使用改方法时,xml文件头需要使用:

1    xmlns:util="http://www.springframework.org/schema/util"
2    xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
3                        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">


引用:




 



dlevin 2012-02-14 18:16
]]>
[多问几个为什么]为什么匿名内部类中引用的局部变量和参数需要final而成员字段不用?http://www.blogjava.net/dlevin/archive/2011/11/23/364599.htmldlevindlevintue, 22 nov 2011 17:49:00 gmthttp://www.blogjava.net/dlevin/archive/2011/11/23/364599.htmlhttp://www.blogjava.net/dlevin/comments/364599.htmlhttp://www.blogjava.net/dlevin/archive/2011/11/23/364599.html#feedback8http://www.blogjava.net/dlevin/comments/commentrss/364599.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/364599.html阅读全文

dlevin 2011-11-23 01:49 发表评论
]]>
历程杂技http://www.blogjava.net/dlevin/archive/2011/09/08/358246.htmldlevindlevinwed, 07 sep 2011 17:15:00 gmthttp://www.blogjava.net/dlevin/archive/2011/09/08/358246.htmlhttp://www.blogjava.net/dlevin/comments/358246.htmlhttp://www.blogjava.net/dlevin/archive/2011/09/08/358246.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/358246.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/358246.html

在使用java线程的时候,我们有时候要调用wait,notifyall,notify来等待或者唤醒线程,如果这几个方法没有包含在synchronized块中,将抛出illegalmonitorstateexception异常,并且当前线程被中断,为什么?

为什么?因为wait,notifyall,notify被调用的时候,都要使用到对象的监视器(锁),但是,如果这些方法不被包含在synchronized块中,那么当前线程就获取不到对象的锁,那么当我们wait的时候,wait根本不知道该释放哪个锁,所以就会抛出不合法的锁异常。

为什么?sleep不需要 被包含在synchronized块中呢?因为sleep不要释放锁,所以也就不抛出异常。

  除去properites文件路径错误、拼写错误外,出现"could not resolve placeholder"很有可能是使用了多个propertyplaceholderconfigurer或者多个的原因。
在spring 3.0中,可以写:
  1. <context:property-placeholder location="xxx.properties" ignore-unresolvable="true" />  
在spring 2.5中,没有ignore-unresolvable属性,此时可以改用propertyplaceholderconfigurer。其实ignore-unresolvable="true" />与下面的配置是等价的
  1. "随便" class="org.springframework.beans.factory.config.propertyplaceholderconfigurer">  
  2.     "location" value="xxx.properties" />  
  3.     "ignoreunresolvableplaceholders" value="true" />   
  4.  

用spring jms,在主线程退出后,进程没有退出情况:
今天写了一个关于spring jms的小程序,发现主线程退出后,但相应的server进程却没有退出。用jconsole查看内部线程情况,发现还有好多线程并没有结束。如图:
在网上没有找到相关的资料,无意中看到貌似可以通过classpathxmlapplicationcontext中的close()方法解决这个问题。对spring和jms其实都不算很了解,不知道这个方法是不是合适或者还有更好的方法,先记下,等以后有时间再好好研究研究。


spring获取插入数据库时自增字段的值:
代码如下:
public int insertsubscriberrecord(int websiteid, 
                                  string firstname, 
                                  string lastname, 
                                  string password, 
                                  string email)
{
  subscriber subscriber 
= new subscriber(websiteid, firstname, lastname, password, email);
  string insertfilestring 
= "insert into subscribers "
    
 "(website_id, first_name, last_name, password, email_address) values "
    
 "(:websiteid, :firstname, :lastname, :password, :emailaddress) ";
  
// see http://static.springframework.org/spring/docs/2.5.x/reference/jdbc.html
  sqlparametersource fileparameters = new beanpropertysqlparametersource(subscriber);
  keyholder keyholder 
= new generatedkeyholder();
  getnamedparameterjdbctemplate().update(insertfilestring, fileparameters, keyholder);
  
return keyholder.getkey().intvalue();
}

参考:

print the stack trace of the exception to a string
 1import java.io.printwriter;
 2import java.io.stringwriter;
 3    public static string getstacktrace(throwable t)
 4    {
 5        stringwriter sw = new stringwriter();
 6        printwriter pw = new printwriter(sw, true);
 7        t.printstacktrace(pw);
 8        pw.flush();
 9        sw.flush();
10        return sw.tostring();
11    }

12
最近在网上看到一个面试题:
1 integer a = null;
2 integer b = a;
3 int c = b;
what will happen?答案当然是nullpointerexception。但是为什么?查看以下代码:
     0  aconst_null
     1  astore_1 [a]
     2  aload_1 [a]
     3  astore_2 [b]
     4  aload_2 [b]
     5  invokevirtual java.lang.integer.intvalue() : int [16]
     8  istore_3 [c]
从字节码中我们可以看出,其实对于装箱和拆箱操作,都是编译器在其中做了支持,将int类型转换成integer类型(调用integer.valueof()方法),或将integer类型转换成int类型(调用integer.intvalue()方法)。
类在什么时候加载为题
 1 class singleton {
 2     private static final singleton instance = new singleton();
 3     
 4     private singleton() {
 5         system.out.println("singleton()");
 6     } 
 7     
 8     public static singleton getinstance() {
 9         return instance;
10     }
11 }
然后当我们有以下一句话:
singleton singleton = null;
or
singleton singleton;
此时类会加载吗?直观点,打印"singleton()"这句话会被执行吗?答案是不会被执行,对第二句话还是好理解的,因为singleton实例根本没有被用到,若要用,首先要初始化所以编译器会最后忽略这句话,所以singleton变量不会出现在字节吗中。对第一句,singleton实例会出现在字节码中,并且会赋null的值,但是此时singleton类还是没有被加载,那么此时singleton这个实例是什么类型呢?这点我有点想不通。或者java的引用在内存中根本是没有类型的,保证类型安全是在编译器端做的,所以在给singleton实例赋null值得时候,只是表明singleton是一个指向null的引用而已,它并没有指向singleton实例,所以此时singleton类不需要加载,只有到真正使用到singleton类的时候才会去加载singleton类,并实例化instance成员。
这样就引出另一个问题:
有人说把初始化放在getinstance()方法中,会使instance在用到时才被加载,而不是刚开始程序初始化时就被加载,在c 中,这个确实是这样的,但是在java中有必要这么做吗?从上述的分析中,我们可以看到,其实java中根本没有必要,只要像上面一样写就可以了。



dlevin 2011-09-08 01:15
]]>
java中的装箱与拆箱http://www.blogjava.net/dlevin/archive/2011/07/20/354743.htmldlevindlevinwed, 20 jul 2011 15:09:00 gmthttp://www.blogjava.net/dlevin/archive/2011/07/20/354743.htmlhttp://www.blogjava.net/dlevin/comments/354743.htmlhttp://www.blogjava.net/dlevin/archive/2011/07/20/354743.html#feedback2http://www.blogjava.net/dlevin/comments/commentrss/354743.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/354743.html

j2se5.0后推出了自动装箱和拆箱的功能,以提高我们的开发效率,然而自动装箱和拆箱实际上是通过编译器来支持的(并非语言本身,或者说虚拟机),因而这种支持也隐藏了部分内部实质,再加上某些类的优化(比如integer里面的缓存等,参看关于缓存节),就更加容易在特定的环境下产生问题,并且如果不知道原来还无法调试。以下先是简单的介绍了编译器对装箱和拆箱的实现,并根据实现简单介绍一下可能会遇到的几个问题。

装箱和拆箱实现

以下装箱和拆箱代码:

       object value = 10;
       
int intvalue = (integer)value;
       integer newintvalue 
= new integer(10);

 

编译成字节码如下:

     0 bipush 10

     2 invokestatic java.lang.integer.valueof(int) : java.lang.integer [20]

     5 astore_1 [value]

     6 aload_1 [value]

     7 checkcast java.lang.integer [21]

    10 invokevirtual java.lang.integer.intvalue() : int [26]

    13 istore_2 [intvalue]

    14 new java.lang.integer [21]

    17 dup

    18 bipush 10

    20 invokespecial java.lang.integer(int) [30]

    23 astore_3 [newintvalue]

从以上字节码可以看到10首先调用valueof方法转换为integer实例,再赋值该value,而value强制转换成integer类后,会调用intvalue方法,后赋值给intvalue。这就是用编译器来实现装箱和拆箱。

 

奇怪的nullpointerexception

查看以下代码:

       integer value = null;
       
int intvalue = value;

 

可以编译通过,但是运行的时候却会发生nullpointerexception。这是由什么引起的呢?依然看一下字节码就可以了:

     0 aconst_null

     1 astore_1 [value]

     2 aload_1 [value]

    3 invokevirtual java.lang.integer.intvalue() : int [20]

     6 istore_2 [intvalue]

从字节码中可以看到,从value赋值该intvalue事实上是直接在value实例上调用intvalue函数。

对当前代码,我们可以一眼就看出当前valuenull的问题,但是如果这个null是在很远以外的地方赋值的呢?或者是间接赋值呢?这个时候遇到这种问题就会比较诡异了。

 

相等与不相等问题

查看一下代码:

       integer value1 = 100;
       integer value2 
= 100;
       system.out.println(
"value1 == value2 is "  (value1 == value2));
      
       integer value3 
= 200;
       integer value4 
= 200;
       system.out.println(
"value3 == value4 is "  (value3 == value4));

 

这段代码会是什么结果?

value1 == value2 is true

value3 == value4 is false

 

两段代码就是值不一样,其他的都一样,竟然会有区别?这个奥妙就因为装箱过程中调用的是valueof方法,而valueof方法对值在-128127之间的数值缓存了(参见关于缓存一节),因而value1value2的引用是相同的,而value3value4的引用是不一样的,而==比较的是引用,因而才会出现以上的结果。

这确的做法应该是:

       integer value1 = 100;
       integer value2 
= 100;
       system.out.println(
"value1 == value2 is "  (value1.equals(value2)));
      
       integer value3 
= 200;
       integer value4 
= 200;

       system.out.println("value3 == value4 is "  (value3.equals(value4))); 

这样的结果就是预料的结果了:

value1 == value2 is true

value3 == value4 is true

 

所以我们要慎用“==”操作符。

 

string中的相等与不等

string中也有类似的情况,查看一下代码:

       string str1 = "abc";
       string str2 
= "abc";
       system.out.println(
"str1 == str2 is "  (str1 == str2));
      
       string str3 
= new string("abc");
       string str4 
= new string("abc");
       system.out.println(
"str3 == str4 is "  (str3 == str4));

 

执行结果:

str1 == str2 is true

str3 == str4 is false

 

这是因为str1str2使用的是同一个字符串,即在字符常量中的字符串,而str3str4在使用字符常量中的字符为参数又创建出了两个新的字符串对象,因而在引用比较情况下是不等的。我们可以从字节码中得到这些信息(删除打印的代码):

     0 ldc [20]

     2 astore_1 [str1]

     3 ldc [20]

     5 astore_2 [str2]

     6 new java.lang.string [22]

     9 dup

    10 ldc [20]

    12 invokespecial java.lang.string(java.lang.string) [24]

    15 astore_3 [str3]

    16 new java.lang.string [22]

    19 dup

    20 ldc [20]

    22 invokespecial java.lang.string(java.lang.string) [24]

    25 astore 4 [str4]

正确的做法还是调用equals方法,而不是使用“==”操作符。

 

关于缓存

据目前信息,有缓存的类有:byteshortintegerlong以及boolean类。而这种缓存也只是在调用valueof(静态)方法的时候才会存在(装箱正是调用了valueof方法)。对整型,缓存的值都是-128127(包括-128127)之间,其他值都不缓存,而对boolean类型只有truefalse值。代码如下:

public final class integer extends number {
    
public static integer valueof(int i) {
        
if(i >= -128 && i <= integercache.high)
            
return integercache.cache[i  128];
        
else
        
return new integer(i);
}
public final class boolean {
    
public static boolean valueof(boolean b) {
        
return (b ? true : false);
    }


2011-01-05

 

 



dlevin 2011-07-20 23:09
]]>
tomcat中jsp使用没有命名空间类的问题http://www.blogjava.net/dlevin/archive/2011/07/20/354742.htmldlevindlevinwed, 20 jul 2011 15:08:00 gmthttp://www.blogjava.net/dlevin/archive/2011/07/20/354742.htmlhttp://www.blogjava.net/dlevin/comments/354742.htmlhttp://www.blogjava.net/dlevin/archive/2011/07/20/354742.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/354742.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/354742.html

问题重现:

今天一刚开始学java的同学在接触jsp的时候遇到了一个比较诡异的问题,他在jsp中始终无法使用自己写的类。简单的演示问题代码:

 

//没有定义包名

public class mydefaultclass {

}

 

//index.jsp文件

<%@ page language="java" contenttype="text/html; charset=iso-8859-1"

    pageencoding="iso-8859-1"%>

doctype html public "-//w3c//dtd html 4.01 transitional//en" "http://www.w3.org/tr/html4/loose.dtd">

<html>

<head>

<title>insert title heretitle>

head>

<body>

<%

    mydefaultclass cls = new mydefaultclass();

%>

load successful

body>

html>

 

出现的错误提示:

type exception report

message

description the server encountered an internal error () that prevented it from fulfilling this request.

exception

org.apache.jasper.jasperexception: unable to compile class for jsp:

 

an error occurred at line: 12 in the jsp file: /index.jsp

mydefaultclass cannot be resolved to a type

9:

10:

11: <%

12:     mydefaultclass cls = new mydefaultclass();

13: %>

14:

15: load successful

 

 

an error occurred at line: 12 in the jsp file: /index.jsp

mydefaultclass cannot be resolved to a type

9:

10:

11: <%

12:     mydefaultclass cls = new mydefaultclass();

13: %>

14:

15: load successful

 

 

stacktrace:

        org.apache.jasper.compiler.defaulterrorhandler.javacerror(defaulterrorhandler.java:92)

        org.apache.jasper.compiler.errordispatcher.javacerror(errordispatcher.java:330)

        org.apache.jasper.compiler.jdtcompiler.generateclass(jdtcompiler.java:439)

        org.apache.jasper.compiler.compiler.compile(compiler.java:349)

        org.apache.jasper.compiler.compiler.compile(compiler.java:327)

        org.apache.jasper.compiler.compiler.compile(compiler.java:314)

        org.apache.jasper.jspcompilationcontext.compile(jspcompilationcontext.java:592)

        org.apache.jasper.servlet.jspservletwrapper.service(jspservletwrapper.java:317)

        org.apache.jasper.servlet.jspservlet.servicejspfile(jspservlet.java:313)

        org.apache.jasper.servlet.jspservlet.service(jspservlet.java:260)

        javax.servlet.http.httpservlet.service(httpservlet.java:717)

 

凯发天生赢家一触即发官网的解决方案:

为什么要特别提示他是刚开始接触java呢?因为我是想强调一下他没有定义包名的习惯,所以才会出现这个问题,而在java中不定义包名是一个非常不好的编程习惯。这个问题本身是可以通过定义自己的包名,然后在jsp文件中导入自己的包解决。

 

问题原因:

由于jsp文件在请求时动态的生成servlet类,然后动态的编译新生成的servlet类,并加载新编译出的servletclass文件,运行相应的service方法,返回结果给客户端。根据栈信息可以知道问题出在了动态编译servlet类的时候,而且是说mydefaultclass无法被解析。

所以我猜测,可能是tomcat在解析jsp文件的时候,没有发现有用户导入的类型(没有使用命名空间,因而无法导入包,也不需要加包名的前缀,导致tomcat在解析jsp文件的时候无法自动感知到当前是引用了用户自定义的类的;也导致在编译的时候却能通过),所以在动态编译的时候没有设置classpath的值,然后导致了这个问题。然而对于这个“bug”,tomcat本身应该不会想不到,何况从逻辑上,默认的把/classes/lib的目录加入到classpath中去编译动态生成的servlet类也是合理的,那么tomcat为什么要这样做呢?我的猜测可能会有两点吧:1. tomcat也认为不定义包名是非常不好的习惯,因而故意留下这个缺陷,以惊醒编程人员。2. 也有可能是出于性能的考虑,如果不加classpath应该是可以提升编译动态生成的servlet类的速度的,因而只要不需要classpath的情况下,默认就不加。

2010-09-22



dlevin 2011-07-20 23:08
]]>
eclipse中异常断点问题http://www.blogjava.net/dlevin/archive/2011/07/20/354741.htmldlevindlevinwed, 20 jul 2011 15:07:00 gmthttp://www.blogjava.net/dlevin/archive/2011/07/20/354741.htmlhttp://www.blogjava.net/dlevin/comments/354741.htmlhttp://www.blogjava.net/dlevin/archive/2011/07/20/354741.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/354741.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/354741.html之前在.net平台下,vs提供的异常断点的工具给我在工作中帮了不好忙,特别是在出问题调试时,给我节省了不少时间。最近开始用eclipse学习java,对异常断点的功能很是怀念,也没有在网上找到过相关资料说eclipse也提供了类似的功能,很是郁闷。今天这个功能终于被我找到了。

eclipse中的“run”中的“add java exception breakpoint…”项,选择要断住的抛出异常类型即可。

在这里选择的exception类型可以在breakpoint窗口中查看或取消:



2010-10-03



dlevin 2011-07-20 23:07
]]>
eclipse中classpath问题http://www.blogjava.net/dlevin/archive/2011/07/20/354740.htmldlevindlevinwed, 20 jul 2011 15:00:00 gmthttp://www.blogjava.net/dlevin/archive/2011/07/20/354740.htmlhttp://www.blogjava.net/dlevin/comments/354740.htmlhttp://www.blogjava.net/dlevin/archive/2011/07/20/354740.html#feedback4http://www.blogjava.net/dlevin/comments/commentrss/354740.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/354740.html

今天发现把jar文件或.class文件放在系统配置的classpath目录下,然而在eclipse的工程中却是无法加载的(通过classloader去加载相应的类)。最后我发现eclipse应该是重新设置了classpath的值了,也正因为这样它才能运行其bin目录下的文件。如打开工程目录下的.classpath文件:

        

        

        

        

 

然而如果加入一下内容:

        

重启eclipse,就可以正确的加载classpath中的内容了。


2011-09-16



dlevin 2011-07-20 23:00
]]>
通过final变量实现条件编译http://www.blogjava.net/dlevin/archive/2011/07/20/354670.htmldlevindlevintue, 19 jul 2011 16:23:00 gmthttp://www.blogjava.net/dlevin/archive/2011/07/20/354670.htmlhttp://www.blogjava.net/dlevin/comments/354670.htmlhttp://www.blogjava.net/dlevin/archive/2011/07/20/354670.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/354670.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/354670.html

首先来比较两段代码所产生的中间代码:

 public class appconfig {

    public static final boolean debug = true;
}
public class debugcode {
    
public static void main(string[] args) {
       
if(appconfig.debug) {
           system.out.println(
"some debug information");
       }
    }
}

debugcode的中间代码(部分):

public class org.levin.insidejvm.miscs.debugcode {

 public static void main(java.lang.string[] args);

    0 getstatic java.lang.system.out : java.io.printstream [16]

    3 ldc [22]

    5 invokevirtual java.io.printstream.println(java.lang.string) : void [24]

    8 return

}

 

public class appconfig {
    
public static final boolean debug = false;
}
public class releasecode {
    
public static void main(string[] args) {
       
if(appconfig.debug) {
           system.out.println(
"some debug information");
       }
    }
}

 

 

releasecode中间代码(部分):

public class org.levin.insidejvm.miscs.releasecode {

 public static void main(java.lang.string[] args);

    0 return

}

 

在上面的代码中,很明显debugcodereleasecode中的代码是一样的,只是appconfig.debug的值不一样而已,却产生了不同的中间代码,即编译器在appconfig.debugfalse的时候直接忽略了if中的语句。利用这个特性,我们就可以根据配置来实现条件编译,从而实现不同的条件产生不同的中间代码而不只是不同的运行结果。

 

然而在这里为什么会出现这样的行为呢?

这是因为编译器对final修饰的基本类型和string类型的变量,在编译时解析为一个本地拷贝,这样拷贝导致编译器在编译的时候明确的知道releasecode的那一段if语句是不会被执行的,因而可以对其做优化。而这种替换的结果也使得用final修饰的int变量可以出现在switch-case语句中。

 

这种方式的缺陷

这种方式的缺陷在于要现实该机制的条件编译,在改变appconfig.debug中的值时,需要同时对appconfig类和releasecode类进行编译(即不能只编译appconfig类)。

 

参考:《深入java虚拟机(第二版)》第八章
2010-09-22



dlevin 2011-07-20 00:23
]]>
equals方法实现小记http://www.blogjava.net/dlevin/archive/2011/06/29/353379.htmldlevindlevinwed, 29 jun 2011 11:05:00 gmthttp://www.blogjava.net/dlevin/archive/2011/06/29/353379.htmlhttp://www.blogjava.net/dlevin/comments/353379.htmlhttp://www.blogjava.net/dlevin/archive/2011/06/29/353379.html#feedback10http://www.blogjava.net/dlevin/comments/commentrss/353379.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/353379.html1)类的每个实例从本质上来说是唯一的,如thread类的实例。
2)我们并不会用到该类的equals方法,如random类,虽然可以比较两个random的实例,以判断两个实例是否可以产生相同的随机数,设计者认为这样的需求用到的场合很少,因而就没有重写equals方法。
3)父类已经实现了equals方法,并且父类实现方式和子类实现方式是一样的,如大部分的set实现的equals方法使用abstractset类提供的equals方法,list实现则使用abstractlist,map实现使用abstractmap的。
4)一个private类或package-private类,我们自己可以确保我们不会使用到它们的equals方法。
同时书也提出一般只有值类型的类才需要实现equals方法,像date、integer、order(作为bean来使用)等。
另外,我们在实现equals方法是也要遵循以下几个原则:
1)自反性(reflexive):x.equals(x)==true
2)对称性(symmetric):x.equals(y)==y.equals(x)
3)传递性(transitive):若x.equals(y)==true, y.equals(z)==true,则x.equals(z)==true。
4)一致性(consistent):多次调用x.equals(y)的结果应该是一样的。
5)对任何非null实例x,x.equals(null)==false。

根据这些特性,我们可以写出如下代码:
 1 public class customer implements serializable {
 2     private static final long serialversionuid = 1l;
 3     
 4     private string id;
 5     private string name;
 6     private string role;
 7     
 8     @override
 9     public boolean equals(object obj) {
10         if(obj == null) {
11             return false;
12         }
13         
14         if(this == obj) {
15             return true;
16         }
17         
18         if(!(obj instanceof customer)) {
19             return false;
20         }
21         
22         customer other = (customer)obj;
23         return (objectutils.equals(id, other.id) && 
24                 objectutils.equals(name, other.name) &&
25                 objectutils.equals(role, other.role));
26     }
27     
28     public string getid() {
29         return id;
30     }
31     public void setid(string id) {
32         this.id = id;
33     }
34     public string getname() {
35         return name;
36     }
37     public void setname(string name) {
38         this.name = name;
39     }
40     public string getrole() {
41         return role;
42     }
43     public void setrole(string role) {
44         this.role = role;
45     }
46 }
其中objectutils类的代码如下:
 1 public class objectutils {
 2     
 3     /**
 4      * compare whether the left and right is equals
 5      * it has already considered the null case
 6      * 
 7      * @param left
 8      * @param right
 9      * @return
10      */
11     public static boolean equals(object left, object right) {
12         if(left == null && right == null) {
13             return true;
14         }
15         if(left == null && right != null) {
16             return false;
17         }
18         return left.equals(right);
19     }
20 }
在《effective java》这本书中,貌似equals实现方法前面没有null、this的判断,因为instanceof可以解决null的问题,而super.equals()方法可以解决this问题,但是我还是喜欢把它们都分出来,这样写的更加明了一些。另外,事实上,这里的实现并没有遵循对称性的原则,因为如果a是b的子类,而这个equals方法在a类中,那么ainstance.equals(binstance)==false,若b也实现了类似的equals方法,则binstance.equals(ainstance)==true(当a没有新的比较字段时,或许这个时候a根本就不需要实现equals方法,如本文开头列出的第三条),这是因为ainstance instanceof binstance == true,反之则为false。不过由于这种情况并不常见,所以就不去care了。:)

    事实上,这里我之所以要记录这些代码,主要是因为有objectutils类的存在。记得以前在学c#的时候,它的object类提供了一个静态的equals方法,我一直对这个方法的存在感到很疑问,直到这次自己写这个equals方法才弄明白,因为虽然在equals方法实现中,最后还要判断类字段是否equals,然后这些字段都有可能是null的,如果没有提供这个静态的equals方法,我们就需要自己来判断每个字段是否为null,然后才可以调用它的equals方法,这样就比较麻烦了,而object.equals方法正是对这种行为的封装,我们只要使用一个方法就可以安全的实现类成员的equals。这也是我加objectutils类的意义所在。希望以后能有机会向这个objectutils类填充更多的实用方法。:)

ps:如一楼所说在commons中的equalsbuilder已经实现了相同的功能,而且代码更加完善,有兴趣的可以看看那里的代码,我这里只是对这次新的的记录,代码只是对当前我考虑的场景中使用,并没有考虑其他方面。另外,在《effective java》中也是建议equals和hashcode两个方法应该是同时实现的,一楼也有说可以用hashcodebuider来实现,这个大家也不妨可以去看看里面的源码,最近时间不多,以后回来再看。。。。。。

dlevin 2011-06-29 19:05 发表评论
]]>
java中的bridge方法http://www.blogjava.net/dlevin/archive/2011/06/23/352917.htmldlevindlevinthu, 23 jun 2011 15:54:00 gmthttp://www.blogjava.net/dlevin/archive/2011/06/23/352917.htmlhttp://www.blogjava.net/dlevin/comments/352917.htmlhttp://www.blogjava.net/dlevin/archive/2011/06/23/352917.html#feedback1http://www.blogjava.net/dlevin/comments/commentrss/352917.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/352917.html

今天在java中字节码的格式的时候,发现method_info中的access_flags中竟然定了acc_bridge的值。网上搜了一下,大概理解它的意思了,先记之。

 

首先是在什么情况下会生成bridge方法(2):

bridge method may be created by the compiler when extending a parameterized type whose methods have parameterized arguments.

这是在网上找到的有人贴出来的一段话,但是感觉这段话说的并不是很明白。首先bridge方式是由编译器产生的,因而在源代码中也没有bridge的关键字。然后只有在以具体类型继承自一个泛型类,同时被继承的泛型类包含了泛型方法。比如看以下的例子:

abstract class a<t> {
    
public abstract t method1(t arg);
    
public abstract t method2();
}
 
class b extends a<string> {
    
public string method1(string arg) {
       
return arg;
    }
    
public string method2() {
       
return "abc";
    }
}
 
class c<t> extends a<t> {
    
public t method1(t arg) {
       
return arg;
    }
   
    
public t method2() {
       
return null;
    }
}

 

 

他们生成的.class文件如下:

a.class

abstract class org.levin.insidejvm.miscs.bridgemethod.a {

 public abstract java.lang.object method1(java.lang.object arg0);

 public abstract java.lang.object method2();

}

b.class

class org.levin.insidejvm.miscs.bridgemethod.b extends org.levin.insidejvm.miscs.bridgemethod.a {

 public java.lang.string method1(java.lang.string arg);

    0 aload_1 [arg]

    1 areturn

 public java.lang.string method2();

    0 ldc [20]

    2 areturn

 public bridge synthetic java.lang.object method2();

    0 aload_0 [this]

    1 invokevirtual org.levin.insidejvm.miscs.bridgemethod.b.method2() : java.lang.string [23]

    4 areturn

  public bridge synthetic java.lang.object method1(java.lang.object arg0);

    0 aload_0 [this]

    1 aload_1 [arg0]

    2 checkcast java.lang.string [26]

    5 invokevirtual org.levin.insidejvm.miscs.bridgemethod.b.method1(java.lang.string) : java.lang.string [28]

    8 areturn

}

c.class

class org.levin.insidejvm.miscs.bridgemethod.c extends org.levin.insidejvm.miscs.bridgemethod.a {

  public java.lang.object method1(java.lang.object arg);

    0 aload_1 [arg]

    1 areturn

 public java.lang.object method2();

    0 aconst_null

    1 areturn

}

可以看到b中生成了两个bridge方法,而c中则没有。事实上,由于java中泛型有擦除的机制,因而在编译a类的时候,它里面定义的方法都是以object类型来表示了,因而如果没有bridge方法,b类根本没有覆盖a类中的abstract方法。正因为有bridge方法的存在,才使得b类可以编译通过。而c类由于在编译时所有的泛型也都是通过object类来表达的,因而它实现的也是a类中的abstract方法,因而不用再生成bridge方法了。

 

事实上b类中的bridge方法在调用也有一些区别:

    public static void main(string[] args) {
       b b 
= new b();
       b.method1(
"abc");
       a
<string> a = new b();
       a.method1(
"abc");
    }

 

这段方法的字节码如下:

     0 new org.levin.insidejvm.miscs.bridgemethod.b [16]

     3 dup

     4 invokespecial org.levin.insidejvm.miscs.bridgemethod.b() [18]

     7 astore_1 [b]

     8 aload_1 [b]

     9 ldc [19]

    11 invokevirtual org.levin.insidejvm.miscs.bridgemethod.b.method1(java.lang.string) : java.lang.string [21]

    14 pop

    15 new org.levin.insidejvm.miscs.bridgemethod.b [16]

    18 dup

    19 invokespecial org.levin.insidejvm.miscs.bridgemethod.b() [18]

    22 astore_2 [a]

    23 aload_2 [a]

    24 ldc [19]

    26 invokevirtual org.levin.insidejvm.miscs.bridgemethod.a.method1(java.lang.object) : java.lang.object [25]

    29 pop

    30 return

以上的代码可以看出b变量调用的method1(string)的方法,而a变量调用的却是method1(object)方法。这种区别也正式因为bridge方法提供的支持才实现的。

 

事实上,bridge方法还会在另外一种情况下产生(2):

java 1.4中,子类若要重写父类某个方法,那么子类的方法和父类的方法签名必须完全一致,包括方法名、参数类型以及返回值;而到java 1.5中,该机制变成,如果子类中某个方法的方法名和参数类型和父类某方法一致,并且子类该方法的返回值是父类相应方法返回值的类型或其子类型,那么该子类方法也可以重写父类中相应的方法。参看以下例子:

class e {
   
}
 
class f extends e {
   
}
 
class x {
    
public e gete() {
       
return new e();
    }
}
 
class y extends x {
    @override
    
public f gete() {
       
return new f();
    }
}

 

以上代码是可以编译通过的。让我们再来查看一下y的字节码:

class org.levin.insidejvm.miscs.bridgemethod.y extends org.levin.insidejvm.miscs.bridgemethod.x {

  public org.levin.insidejvm.miscs.bridgemethod.f gete();

    0 new org.levin.insidejvm.miscs.bridgemethod.f [16]

    3 dup

    4 invokespecial org.levin.insidejvm.miscs.bridgemethod.f() [18]

    7 areturn

 public bridge synthetic org.levin.insidejvm.miscs.bridgemethod.e gete();

    0 aload_0 [this]

    1 invokevirtual org.levin.insidejvm.miscs.bridgemethod.y.gete() : org.levin.insidejvm.miscs.bridgemethod.f [20]

    4 areturn

}

从字节码上,我们可以看出语法本身事实上并没有发生变化,变化的只是编译器做的支持,它为重载方法重新生成了一个返回e而不是fbridge方法。

从调用的字节码上可以更加明显的看出语法没有发生变化这一点:

    public static void main(string[] args) {
       x x 
= new y();
       x.gete();
    }

 

字节码如下:

 public static void main(java.lang.string[] args);

     0 new org.levin.insidejvm.miscs.bridgemethod.y [16]

     3 dup

     4 invokespecial org.levin.insidejvm.miscs.bridgemethod.y() [18]

    7 astore_1 [x]

     8 aload_1 [x]

     9 invokevirtual org.levin.insidejvm.miscs.bridgemethod.x.gete() : org.levin.insidejvm.miscs.bridgemethod.e [19]

    12 pop

13 return

该字节码中x.gete()方法事实上调用的就是生成的bridge方法(e gete())方法,而不是用户定义的f gete()方法。

这种重载机制在某些,不同子类某个函数的返回值是不一样的,但是他们都需要重写父类中方法,以可以在某个点上通过父类实例统一调用。只是这种机制就需要返回值必须是继承于同一个类。事实上,这种方式在没有引入这种重写机制的时候也是可以实现的,只是现在java在编译器层面上提供了支持。

                                                                                                                    于2010年10月3日
注:这些文章都是前些时候写的,之前博客很乱,也都是随便贴一些自己写的或转载的,还有一些则是没有贴出来过的。现在打算好好整理一下,完整的记录自己的一些学习历程,而每次看到过去的时间,则让我想起以前的日子,因而我对时间一直是很重视的,所以每篇都著名写的日期,直到最先的文章出现。:)



dlevin 2011-06-23 23:54
]]>
java接口中的字段http://www.blogjava.net/dlevin/archive/2011/06/23/352916.htmldlevindlevinthu, 23 jun 2011 15:51:00 gmthttp://www.blogjava.net/dlevin/archive/2011/06/23/352916.htmlhttp://www.blogjava.net/dlevin/comments/352916.htmlhttp://www.blogjava.net/dlevin/archive/2011/06/23/352916.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/352916.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/352916.html

java接口中的字段默认都是静态常量,不管在编码的时候有没有显示的指定static或者final

 

如以下接口:

public interface accessflag {
    
public int a = 10;
    
public static int b = 20;
    
public final int c = 30;
    
public static final int d = 40;
}

 

它编译生成的二进制代码如下:

public abstract interface org.levin.classfilereader.accessflag {

 public static final int a = 10;

 public static final int b = 20;

 public static final int c = 30;

 public static final int d = 40;

}

                                                                                                                    于2010年9月4日
注:这些文章都是前些时候写的,之前博客很乱,也都是随便贴一些自己写的或转载的,还有一些则是没有贴出来过的。现在打算好好整理一下,完整的记录自己的一些学习历程,而每次看到过去的时间,则让我想起以前的日子,因而我对时间一直是很重视的,所以每篇都著名写的日期,直到最先的文章出现。:)


dlevin 2011-06-23 23:51
]]>
static变量和实例变量的初始化顺序问题http://www.blogjava.net/dlevin/archive/2011/06/20/352691.htmldlevindlevinmon, 20 jun 2011 14:53:00 gmthttp://www.blogjava.net/dlevin/archive/2011/06/20/352691.htmlhttp://www.blogjava.net/dlevin/comments/352691.htmlhttp://www.blogjava.net/dlevin/archive/2011/06/20/352691.html#feedback2http://www.blogjava.net/dlevin/comments/commentrss/352691.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/352691.html 1 public class staticinitsequence { 2  ...  

dlevin 2011-06-20 22:53
]]>
void类的用法http://www.blogjava.net/dlevin/archive/2011/06/20/352690.htmldlevindlevinmon, 20 jun 2011 14:48:00 gmthttp://www.blogjava.net/dlevin/archive/2011/06/20/352690.htmlhttp://www.blogjava.net/dlevin/comments/352690.htmlhttp://www.blogjava.net/dlevin/archive/2011/06/20/352690.html#feedback2http://www.blogjava.net/dlevin/comments/commentrss/352690.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/352690.html

今天在看classloader源码的时候,突然发现里面有一个叫void的类使用,代码看起来挺新颖的,摘下来,以备后用。

public abstract class classloader {
    private static void checkcreateclassloader() {
        ...
        return null;

    }

    private classloader(void unused, classloader parent) {

        this.parent = parent;

    }

    protected classloader(classloader parent) {

        this(checkcreateclassloader(), parent);

    }
    protected classloader() {

        this(checkcreateclassloader(), getsystemclassloader());

}

}

checkcreateclassloader()方法用返回void类型,该函数返回null,然后它就可以在其他函数的参数中调用了,只要该参数也是void类型的。

这种方式绕开了java中不能在函数参数中使用void类型的局限。

 

可以作为部分参考吧。呵呵。

                                                                                                                     于2010年9月15日

注:这些文章都是前些时候写的,之前博客很乱,也都是随便贴一些自己写的或转载的,还有一些则是没有贴出来过的。现在打算好好整理一下,完整的记录自己的一些学习历程,而每次看到过去的时间,则让我想起以前的日子,因而我对时间一直是很重视的,所以每篇都著名写的日期,直到最先的文章出现。:)


dlevin 2011-06-20 22:48
]]>
list或数组到string的转换http://www.blogjava.net/dlevin/archive/2011/06/20/352657.htmldlevindlevinmon, 20 jun 2011 05:27:00 gmthttp://www.blogjava.net/dlevin/archive/2011/06/20/352657.htmlhttp://www.blogjava.net/dlevin/comments/352657.htmlhttp://www.blogjava.net/dlevin/archive/2011/06/20/352657.html#feedback0http://www.blogjava.net/dlevin/comments/commentrss/352657.htmlhttp://www.blogjava.net/dlevin/services/trackbacks/352657.html或string[]的形式存在,此时就需要做list或数组到string的转换。
在c#中,string类提供了一个叫join的方法,因而我们使用一句代码就可以完成这样的工作:
string result = string.join("", tolist.toarray());
但是在java中貌似没有提供类似的方法(至少我还没有找到),既然没有提供,我们就可以自己来写一个:
public class stringutils {
    
/**
     * stringuitls类中的方法都是静态的,
     * 阻止其创建实例,以引起一些误解。
     * 另一种做法是将其变为abstract类,
     * 但是这样也会引起一些误解,
     * 因为abstract类的意思就是可以继承的,
     * 然而该类却不应该由其他类来继承。
     * 因而abstract类个人感觉不是一种好的做法。
     
*/
    
private stringutils() { }
    
    
/**
     * 将array中的内容以delimiter为间隔拼接字符串
     * 
     * 
@param array
     * 
@param delimiter
     * 
@return
     
*/
    
public static string join(object[] array, string delimiter) {
        
if(array == null) {
            
throw new illegalargumentexception();
        }
        
        
if(array.length == 0) {
            
return "";
        }
        
        stringbuilder builder 
= new stringbuilder();
        
for(object item : array) {
            builder.append(item.tostring() 
 delimiter);
        }
        builder.delete(builder.length() 
- delimiter.length(), builder.length());
        
return builder.tostring();
    }
    
    
/**
     * 将list中的内容以delimiter为间隔拼接字符串
     * 
     * 
@param list
     * 
@param delimiter
     * 
@return
     
*/
    @suppresswarnings(
"unchecked")
    
public static string join(list list, string delimiter) {
        
if(list == null) {
            
throw new illegalargumentexception();
        }
        
        
return join(list.toarray(), delimiter);
    }
}

测试代码如下:
 1     @test
 2     public void testlisttostring() {
 3         list<string> list = new arraylist<string>();
 4         list.add("item1");
 5         list.add("item2");
 6         list.add("item3");
 7         
 8         string expected = "[item1, item2, item3]";
 9         
10         assertequals(expected, list.tostring());
11     }
12     
13     @test
14     public void testlisttostring2() {
15         list<string> list = new arraylist<string>();
16         list.add("item1");
17         list.add("item2");
18         list.add("item3");
19         
20         string expected = "item1;item2;item3";
21         
22         assertequals(expected, stringutils.join(list, ";"));
23     }



dlevin 2011-06-20 13:27
]]>
网站地图