编程 Archive

0

java泛型的擦除以及桥方法

java泛型的擦除
在1.5之后, java语言中的泛型编程得到了广泛的运用. 但是为了使得java语言在引入泛型之后, 能够兼容以前java的非泛型代码, java的泛型特性做了相当的阉割–即对于泛型的类型信息(type information), jvm采取了摒弃的策略. 造成了以下的事实.

Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().getClass();
System.out.println(c1 == c2); //true

“The cold truth is:There’s no information about generic parameter types available inside generic code”–对于List 和 List 这两种泛型类型来说, 他们的运行时类型是一样的, 事实上,jvm并不知道泛型,所有的泛型在编译阶段就已经被处理成了普通类和方法。jvm会认为都是List类型, List中的元素的”String”类型和”Integer”类型就给擦除(erased)掉了. 虽然擦除掉了, 但我们知道, java是强类型语言, 不管什么元素, 你总得有一个类型才行, 否则jvm就没法处理, 编译器也不会过. 那List里的元素被认为成什么类型呢?
一般说来, 泛型的类型尖括号里面的泛型类型, 在编译时会被处理成一个原始类型.
如果有限定(),jvm就A作为原始类型
如果有多个限定(
),jvm就用第一个边界的类型变量A类作为原始类型
如果泛型类型的类型变量没有限定(
) ,等同于(), 所以就用Object作为原始类型

Read the rest of this entry »

0

遇到的死锁问题分析

转载请注明出处: http://www.liubida.com/programming_language/dead_lock_analysis
前几天遇到一个问题, 导致连续3天的虚拟机数据没有更新成功, 后来通过监控发现每天的定时任务会报出大量的异常, 异常信息直指数据库的DeadLock
异常和堆栈的部分信息如下:
com.alibaba.armory.exception.ArmoryServiceException: SqlMapClient operation; SQL [];
--- The error occurred in ibatis/VmServer.xml.
--- The error occurred while applying a parameter map.
--- Check the VmServer.updateVMStateByHostId-InlineParameterMap.
--- Check the statement (update failed).
--- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:
--- The error occurred in ibatis/VmServer.xml.
--- The error occurred while applying a parameter map.
--- Check the VmServer.updateVMStateByHostId-InlineParameterMap.
--- Check the statement (update failed).
--- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
at com.alibaba.armory.exchange.dragoon.data.DragoonDataHandler.refreshVMToHost(DragoonDataHandler.java:432)
at ...

执行的定时任务是对宿主机刷新数据. 任务由很多线程执行, 并且在涉及到数据库update的方法时, 使用了spring的@Transactional来标记为事务处理, 我们定义为事务A.
比较关键的几个sql语句,
1. select lock_state from server where id = ? for update
2. update server set lock_state = ? where id = ?
3. select s.*, d.*, s.id as server_id, ast.po_number as po, ast.in_service_date as buy_time, ast.cost_value as original_cost, v.host_id as host_id, v.vm_index as vm_index, v.vm_resource as vm_resource, v.state as vm_state, v.op_state as op_state, cpu.amount as cpu_amount, cpu.core_num as cpu_core, mssize as mem_size from server s left join device_base d on s.device_base_id = d.id left join device_base_asset ast on ast.tag_number = d.assets_num right join vmserver v on v.server_id = s.id left join cpu on s.id = cpu.server_id left join (select server_id,sum(size) mssize from memory_slot group by server_id) mstmp on mstmp.server_id = s.id where v.host_id in ( ? )
4. update vmserver set state = ? where host_id = ?
5. update server set lock_state = ? where id = ?
Read the rest of this entry »

0

质数小求

转载请注明出处: http://www.liubida.com/programming_language/prime/
 
什么是质数?
1. 大于1的正整数
2. 只能被1和自己整除
满足1和2的就是一个质数(也叫素数),反之被叫做合数。
 
质数相关的东西
数学上,任何一个整数均可表示为质数的乘积,所以质数在数学上很重要。虽然从质数中很难得到一些定理。
公元前3世纪,古希腊数学家欧几里德证明了质数的数目是无穷的。而最近几年,由陶哲轩和格林证明了“存在任意长度的质数等差数列”这一重要命题。我不打算就这些个数学证明写点什么,就简单的就这个命题举一些例子吧。
命题:存在任意长度的质数等差数列,它的意思:能够找到一个由质数构成的等差数列,其长度为指定的L。
比如,长度为5的质数等差数列:5,17,29,41,53;
长度为10的质数等差数列:199,409,619,829,1039,1249,1459,1669,1879,2089;
到目前为止,已知的质数等差数列的长度为23,其数列的第一个数是56211383760397,间隔常数为44546738095860。
对于一个随机的正整数n来说,它有50%的几率能被2整除,33%的几率能被3整除。可以计算出,n有88%的几率能够被100以内的某个数整除,有92%的几率能够被1000以内的某个数整除。基于这些分析,关于质数分布的事实是:随着正整数数值的增加,质数的分布变得越来越稀疏。
 
那么,我们的问题是?
如何判断正整数n是否为一个质数?
请列出1000,10000以内的质数?
现在求质数的方法有很多,wiki上列举了AKS/Elliptic curve/Miller-Rabin/Solovay-Strassen/Fermat等这些专业方法。但是我们平时用的一般是尝试相除法和筛选法。
Read the rest of this entry »

0

lucene的分词和过滤简介

转载请注明出处: http://www.liubida.com/uncategorized/lucene_analyzer_intro/

没接触过lucene的童鞋可以看看 这篇文章, 算是lucence入门比较好的文章, 读完之后可以对lucene有个简单的认识. 我也是最近有项目用到了lucene, 具体实践是对数据库的某个字段做索引, 实现了一个针对该字段匹配查询. 有点意思. 然后就趁机看了下lucene中分析器(Analyzer)的一些代码, 觉得它的分词和过滤的设计还是蛮有意思的.

lucene的分析器的功能就是对输入的数据进行分词(对文本资源进行切分, 将文本按规则切分为一个个可以进入索引的最小单位)和过滤(对最小单位进行预处理, 比如大写转小写). 在lucene中, 所有的分析器都继承自抽象类Analyzer(org.apache.lucene.analysis.Analyzer). Analyzer的代码里只定义了两个毫无意义的方法. 如下所示:

/** An Analyzer builds TokenStreams, which analyze text.  It thus represents a
*  policy for extracting index terms from text.
*  <p>
*  Typical implementations first build a Tokenizer, which breaks the stream of
*  characters from the Reader into raw Tokens.  One or more TokenFilters may
*  then be applied to the output of the Tokenizer.
*  <p>
*  WARNING: You must override one of the methods defined by this class in your
*  subclass or the Analyzer will enter an infinite loop.
*/
public abstract class Analyzer {
  /** Creates a TokenStream which tokenizes all the text in the provided
    Reader.  Default implementation forwards to tokenStream(Reader) for
    compatibility with older version.  Override to allow Analyzer to choose
    strategy based on document and/or field.  Must be able to handle null
    field name for backward compatibility. */
  public TokenStream tokenStream(String fieldName, Reader reader)
  {
      // implemented for backward compatibility
      return tokenStream(reader);
  }
 
  /** Creates a TokenStream which tokenizes all the text in the provided
   *  Reader.  Provided for backward compatibility only.
   * @deprecated use tokenStream(String, Reader) instead.
   * @see #tokenStream(String, Reader)
   */
  public TokenStream tokenStream(Reader reader)
  {
      return tokenStream(null, reader);
  }
}

分析器的分词功能由Tokenizer的子类来完成, 过滤功能由TokenFilter的子类来完成. 同样的, 这也是两个虚无飘渺的抽象类. 下面我直接以StandardAnalyzer为实例来讲述分词和过滤的过程.
StandardAnalyzer是lucene开发包中内置的一种Analyzer的实现, 这个分析器是最容易使用也是使用很频繁的一种Analyzer, 它使用了lucene内部自带的几种分词器和过滤器.
在其代码中, 可以看到StandardAnalyzer也是继承了Analyzer, 并且实现了抽象类Analyzer的tokenStream方法. 我们注意到在这个方法里, reader首先是作为参数创建了一个分词器StandardTokenizer, 然后用这个分词器依次创建了LowerCaseFilter(大写转小写)和StopFilter(停止词过滤器)这两个过滤器. 目前为止, 基本还是一头雾水, 完全不知道分词和过滤是怎样一个过程? 其实, 到目前为止, tokenStream方法只是利用分词器, 过滤器在reader这个读入流后面铺设了一个管道而已, 真正的分词和过滤过程是在另一个地方执行的. 打个比方, reader就好像是自来水管道, tokenStream所做的只是接着这个管道又铺设了一段管道, 并且这一段的管道是用StandardTokenizer这种材质制造的, 能够对流过的文本进行切分, 然后又加上了LowerCaseFilter和StopFilter这两个过滤网. Read the rest of this entry »

1

java范型中的extends和super

转载请注明出处:http://www.liubida.com/uncategorized/extendsandsuper

package com.liubida.ThinkingInJAVA.generics;

import java.util.ArrayList;
import java.util.List;

/**
* @author liubida
*/
class E{}
class D extends E{}
class C extends D{}
class B extends C{}
final class A extends B{}

public class GenericExtendsSuper {
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        /**
         * List<? extends E> 表明:list中的元素的类型的上届是类E
         * 编译器只知道list可能存放类E,或者E的子类,编译器无法知道list中元素到底是什么类型
         *
         */
        List<? extends E> list = new ArrayList<D>();
        /**
         * 这里,list.add(anything)都是会编译出错
         * 编译器的逻辑:如果list中的元素是E或者E的子类,那么这个赋值就有可能是非法的
         * list.add(new D());   —> 编译器想,可能list中元素是A,那么参数传入就存在这样的赋值: A tmp = new D(); 所以报错
         *
         * —分割线——————————
         * list.add(new A());   —> 对于这个也报错,我觉得是编译器没想明白。注意这时候类A是final class,即不能再有子类。
         *                           按照上文的逻辑,编译器如果能想到list中元素的类型不可能是A的子类,那么参数传入的赋值是合法的。
         *                           但是依然报错,显然就是编译器就把这一条特殊情况下的思路给忽略了。
         *
         */
//        list.add(new Object()); //编译错误
//        list.add(new E());      //编译错误
//        list.add(new D());      //编译错误
//        list.add(new C());      //编译错误
//        list.add(new B());      //编译错误
//        list.add(new A());      //编译错误
        list.add(null);         //正确,但是没有任何意义。null没有类型信息,

        /**
         * list中元素的类型的上届是类E,所以用E去接收list.get(0)出来的元素是合法的
         * 编译器想,如果list中元素类型是E,那用D去接收就是非法,所以报错
         *
         */
        E e = list.get(0);
//        D d = list.get(0);      //编译错误
//        C c = list.get(0);      //编译错误
//        B b = list.get(0);      //编译错误
//        A a = list.get(0);      //编译错误
       
        /**
         * List<? super B> 表明:list中的元素的类型的下届是类B
         * 编译器只知道list可能存放类B,或者B的父类,编译器无法知道list中元素到底是什么类型
         *
         */       
        List<? super B> alist = new ArrayList<D>();
        /**
         * alist.add(O): 如果O>B,则报错;如果O<=B,则编译通过
         * 编译器的逻辑:如果alist中的元素是B或者B的父类,如果O>B,那么这个赋值就有可能是非法的;如果O<=B,那么这个赋值就是合法的。
         * alist.add(new E());   —> 编译器想,可能alist中元素是B,那么参数传入就存在这样的赋值: B tmp = new E(); 所以报错
         * alist.add(new A());   —> 编译器想,alist中元素类型最低也是B,那么参数传入就存在这样的赋值: B tmp = new A(); 所以通过
         *
         */
//      alist.add(new Object()); //编译错误
//      alist.add(new E());      //编译错误
//      alist.add(new D());      //编译错误
//      alist.add(new C());      //编译错误
        alist.add(new B());     
        alist.add(new A());     
       
        /**
         * alist中元素的类型的下界是类B,list.get(0)返回的类型可能是B,C,D,E… Object
         * D d1 = alist.get(0)       —> 编译器想,可能alist中元素是E,那么就存在这样的赋值: D d1 = new E(); 所以报错
         *
         * —分割线——————————
         * Object o1 = alist.get(0); —> Object是所有类的父类,是最大的类。
         *                                alist中所有元素的类都不能大于Object,所以这个get是通过的。
         *
         */
        Object o1 = alist.get(0);
//        E e1 = alist.get(0);      //编译错误
//        D d1 = alist.get(0);      //编译错误
//        C c1 = alist.get(0);      //编译错误
//        B b1 = alist.get(0);      //编译错误
//        A a1 = alist.get(0);      //编译错误
       
       
        /**
         * “?”代表未知类型
         * extends关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
         * super关键字声明了类型的下界,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object
         */
    }
}

0

回车符和换行符

转载请注明出处: http://www.liubida.com/programming_language/randn/

今天调试一个代码, 用java的Process执行了一个命令, 然后从Process的InputStream里读出了命令的返回信息. 遇到了一些关于回车符和换行符的问题. 索性仔细研究下.
\r = 回车 = carriage return = CR = 13
\n = 换行 = line feed = LF = 10

回车符与换行符的起源 转自这里
计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。
一个叫做“回车”,‘\r’,ASCII(13),告诉打字机把打印头定位在左边界;
一个叫做“换行”,‘\n’,ASCII(10)告诉打字机把纸向下移一行。
这就是“回车”和“换行”的来历。
后来,计算机发明了,这两个概念也就被般到了计算机上。但是在初期,存储器很贵,在每行结尾加两个字符太浪费了,只加一个也可以实现相应的功能。于是,不同的系统就出现了分歧。
Unix系统里,每行结尾只有“< 换行>”,即’\n‘,’10‘;
Windows系统里面,每行结尾是“< 回车>< 换行>”,即“\r\n”,“13 10”;
Mac系统里,每行结尾是“< 回车>”,即’\r‘,’13‘。
一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。

实践小例:
Linux系统下test.o文件的信息:
aaa
bbb
ccc

public static void main(String[] args) throws IOException {
    File f = new File("/home/liubida/test.o");
    FileInputStream fi = new FileInputStream(f);
    int c = 0;
    while (-1 != (c = fi.read())) {
        System.out.print(c+"#");
    }
}

输出结果:
97#97#97#10#98#98#98#10#99#99#99#10#
说明Unix系统中,文件只用换行符’10‘来换行。

ipmitool使用中遇到的回车, 换行的问题分析.
输出A: 通过ipmitool进入sol模式, 进入os交互, 键入ls, 在shell中得到返回的信息. Read the rest of this entry »

0

String StringBuffer StringBuilder

转载请注明出处: http://www.liubida.com/programming_language/string-stringbuffer-stringbuilder/

昨天又碰到了String的问题, 索性在网上看了个明白, 其中这篇帖子String in Java讲的最透彻, 推荐一下.
看完了这个帖的内容, 相信应该对这三个恼人的东西比较清楚了, 我做了个简单的小结.
String s1 = "123";
String s2 = new String("123");
String s3 = s2.intern();

1. String是个对象. “123″常量在类被加载的时候, 由JVM自动的在string_pool中生成了一个拘留字符串(intern_string…谁能告诉我这个到底叫什么字符串, 这个名字太秀逗了…)
s1只是一个变量, 它直接指向了string_pool中的那个拘留字符串. id=15.
s2是一个变量, 它指向了堆上的一个字符串对象, 该对象是由同一个拘留字符串初始化的. id=21.
s3是一个变量, 通过intern()方法, 使他指向了初始化这个堆对象的拘留字符串. id=15.

所以s1!=s2; s1=s3;
2. String不可变是因为内部的char[]是final, 而String[Buffer|Builder]是可变的char[] Read the rest of this entry »