java基础知识总结

空集103

贡献于2015-12-01

字数:81067 关键词: Java开发

1.JAVA的StringBuffer类 StringBuffer类和String一样,也用来代表字符串,只是由于StringBuffer的内部实现方式和String不同,所以StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。 所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。 在StringBuffer类中存在很多和String类一样的方法,这些方法在功能上和String类中的功能是完全一样的。 但是有一个最显著的区别在于,对于StringBuffer对象的每次修改都会改变对象自身,这点是和String类最大的区别。 另外由于StringBuffer是线程安全的,关于线程的概念后续有专门的章节进行介绍,所以在多线程程序中也可以很方便的进行使用,但是程序的执行效率相对来说就要稍微慢一些。 1、StringBuffer对象的初始化 StringBuffer对象的初始化不像String类的初始化一样,Java提供的有特殊的语法,而通常情况下一般使用构造方法进行初始化。 例如: StringBuffer s = new StringBuffer(); 这样初始化出的StringBuffer对象是一个空的对象。 如果需要创建带有内容的StringBuffer对象,则可以使用: StringBuffer s = new StringBuffer(“abc”); 这样初始化出的StringBuffer对象的内容就是字符串”abc”。 需要注意的是,StringBuffer和String属于不同的类型,也不能直接进行强制类型转换,下面的代码都是错误的: StringBuffer s = “abc”; //赋值类型不匹配 StringBuffer s = (StringBuffer)”abc”; //不存在继承关系,无法进行强转 StringBuffer对象和String对象之间的互转的代码如下: String s = “abc”; StringBuffer sb1 = new StringBuffer(“123”); StringBuffer sb2 = new StringBuffer(s); //String转换为StringBuffer String s1 = sb1.toString(); //StringBuffer转换为String 2、StringBuffer的常用方法 StringBuffer类中的方法主要偏重于对于字符串的变化,例如追加、插入和删除等,这个也是StringBuffer和String类的主要区别。 a、append方法 public StringBuffer append(boolean b) 该方法的作用是追加内容到当前StringBuffer对象的末尾,类似于字符串的连接。调用该方法以后,StringBuffer对象的内容也发生改变,例如: StringBuffer sb = new StringBuffer(“abc”); sb.append(true); 则对象sb的值将变成”abctrue”。 使用该方法进行字符串的连接,将比String更加节约内容,例如应用于数据库SQL语句的连接,例如: StringBuffer sb = new StringBuffer(); String user = “test”; String pwd = “123”; sb.append(“select * from userInfo where username=“) .append(user) .append(“ and pwd=”) .append(pwd); 这样对象sb的值就是字符串“select * from userInfo where username=test and pwd=123”。 b、deleteCharAt方法 public StringBuffer deleteCharAt(int index) 该方法的作用是删除指定位置的字符,然后将剩余的内容形成新的字符串。例如: StringBuffer sb = new StringBuffer(“Test”); sb. deleteCharAt(1); 该代码的作用删除字符串对象sb中索引值为1的字符,也就是删除第二个字符,剩余的内容组成一个新的字符串。所以对象sb的值变为”Tst”。 还存在一个功能类似的delete方法: public StringBuffer delete(int start,int end) 该方法的作用是删除指定区间以内的所有字符,包含start,不包含end索引值的区间。例如: StringBuffer sb = new StringBuffer(“TestString”); sb. delete (1,4); 该代码的作用是删除索引值1(包括)到索引值4(不包括)之间的所有字符,剩余的字符形成新的字符串。则对象sb的值是”TString”。 c、insert方法 public StringBuffer insert(int offset, boolean b) 该方法的作用是在StringBuffer对象中插入内容,然后形成新的字符串。例如: StringBuffer sb = new StringBuffer(“TestString”); sb.insert(4,false); 该示例代码的作用是在对象sb的索引值4的位置插入false值,形成新的字符串,则执行以后对象sb的值是”TestfalseString”。 d、reverse方法 public StringBuffer reverse() 该方法的作用是将StringBuffer对象中的内容反转,然后形成新的字符串。例如: StringBuffer sb = new StringBuffer(“abc”); sb.reverse(); 经过反转以后,对象sb中的内容将变为”cba”。 e、setCharAt方法 public void setCharAt(int index, char ch) 该方法的作用是修改对象中索引值为index位置的字符为新的字符ch。例如: StringBuffer sb = new StringBuffer(“abc”); sb.setCharAt(1,’D’); 则对象sb的值将变成”aDc”。 f、trimToSize方法 public void trimToSize() 该方法的作用是将StringBuffer对象的中存储空间缩小到和字符串长度一样的长度,减少空间的浪费。 总之,在实际使用时,String和StringBuffer各有优势和不足,可以根据具体的使用环境,选择对应的类型进行使用。 2.Java中replace()、replaceFirst()和replaceAll()区别 str.replace(str中被替换的,替换后的字符) replace和replaceAll是JAVA中常用的替换字符的方法,它们的区别是:   1)replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换(CharSequence即字符串序列的意思,说白了也就是字符串);   2)replaceAll的参数是regex,即基于规则表达式的替换,比如,可以通过replaceAll("\\d", "*")把一个字符串所有的数字字符都换成星号;   相同点是都是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或字符串,如果只想替换第一次出现的,可以使用 replaceFirst(),这个方法也是基于规则表达式的替换,但与replaceAll()不同的是,只替换第一次出现的字符串;   另外,如果replaceAll()和replaceFirst()所用的参数据不是基于规则表达式的,则与replace()替换字符串的效果是一样的, 即这两者也支持字符串的操作; 还有一点注意: 执行了替换操作后,源字符串的内容是没有发生改变的(因为String 类是final类型的不可改写,但可以把处理得到的结果赋值).   举例如下:   String src = new String("ab43a2c43d");   System.out.println(src.replace("3","f"));=>ab4f2c4fd.   System.out.println(src.replace('3','f'));=>ab4f2c4fd.   System.out.println(src.replaceAll("\\d","f"));=>abffafcffd.   System.out.println(src.replaceAll("a","f"));=>fb43fc23d.   System.out.println(src.replaceFirst("\\d,"f"));=>abf32c43d   System.out.println(src.replaceFirst("4","h"));=>abh32c43d.   如何将字符串中的"\"替换成"\\":   String msgIn;   String msgOut;   msgOut=msgIn.replaceAll("\\\\","\\\\\\\\");   原因: '\'在java中是一个转义字符,所以需要用两个代表一个。例如System.out.println( "\\" ) ;只打印出一个"\"。 但是'\'也是正则表达式中的转义字符(replaceAll 的参数就是正则表达式),需要用两个代表一个。所以:\\\\被java转换成\\,\\又被正则表达式转换成\。   同样   CODE: \\\\\\\\   Java: \\\\   Regex: \\   将字符串中的'/'替换成'\'的几种方式:   msgOut= msgIn.replaceAll("/", "\\\\"); msgOut= msgIn.replace("/", "\\"); 3.window.location和window.open的区别 window.location = "http://www.xxxxxxxx.net" 跳转后有后退功能 window.location.replace("http://www.xxxxxxxx.net") 跳转后没有后退功能 window.open("http://www.xxxxxxxx.net") 要新的窗口打开链接 4.Class.forName的作用以及为什么要用它【转】 Posted on 2010-03-03 10:24 火之光 阅读(674) 评论(0) 编辑 收藏 Class.forName(xxx.xx.xx) 返回的是一个类 首先你要明白在java里面任何class都要装载在虚拟机上才能运行。这句话就是装载类用的(和new 不一样,要分清楚)。 至于什么时候用,你可以考虑一下这个问题,给你一个字符串变量,它代表一个类的包名和类名,你怎么实例化它?只有你提到的这个方法了,不过要再加一点。 A a = (A)Class.forName("pacage.A").newInstance(); 这和你 A a = new A(); 是一样的效果。 关于补充的问题 答案是肯定的,jvm会执行静态代码段,你要记住一个概念,静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了。而且以后不会再走这段静态代码了。 Class.forName(xxx.xx.xx) 返回的是一个类 Class.forName(xxx.xx.xx);的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段 动态加载和创建Class 对象,比如想根据用户输入的字符串来创建对象 String str = 用户输入的字符串 Class t = Class.forName(str); t.newInstance(); 在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字除了一个是方法,一个是关键字外,最主要有什么区别?它们的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。那么为什么会有两种创建对象方式?这主要考虑到软件的可伸缩、可扩展和可重用等软件设计思想。 Java中工厂模式经常使用newInstance()方法来创建对象,因此从为什么要使用工厂模式上可以找到具体答案。 例如: class c = Class.forName(“Example”); factory = (ExampleInterface)c.newInstance(); 其中ExampleInterface是Example的接口,可以写成如下形式: String className = "Example"; class c = Class.forName(className); factory = (ExampleInterface)c.newInstance(); 进一步可以写成如下形式: String className = readfromXMlConfig;//从xml 配置文件中获得字符串 class c = Class.forName(className); factory = (ExampleInterface)c.newInstance(); 上面代码已经不存在Example的类名称,它的优点是,无论Example类怎么变化,上述代码不变,甚至可以更换Example的兄弟类Example2 , Example3 , Example4……,只要他们继承ExampleInterface就可以。 从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用newInstance()方法的时候,就必须保证:1、这个类已经加载;2、这个类已经连接了。而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载java API的那个加载器。 现在可以看出,newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。 这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。 最后用最简单的描述来区分new关键字和newInstance()方法的区别: newInstance: 弱类型。低效率。只能调用无参构造。 new: 强类型。相对高效。能调用任何public构造。 5.Hibernate包作用详解【转】 Posted on 2009-12-23 11:32 火之光 阅读(168) 评论(0) 编辑 收藏 Hibernate一共包括了23个jar包,令人眼花缭乱。本文将详细讲解Hibernate每个jar包的作用,便于你在应用中根据自己的需要进行取舍。 下载Hibernate,例如2.0.3稳定版本,解压缩,可以看到一个hibernate2.jar和lib目录下有22个jar包: · hibernate2.jar: Hibernate的库,没有什么可说的,必须使用的jar包 · cglib-asm.jar: CGLIB库,Hibernate用它来实现PO字节码的动态生成,非常核心的库,必须使用的jar包 · dom4j.jar: dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。dom4j是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在IBM developerWorks上面可以找到一篇文章,对主流的Java XML API进行的性能、功能和易用性的评测,dom4j无论在那个方面都是非常出色的。我早在将近两年之前就开始使用dom4j,直到现在。如今你可以看到越来越多的Java软件都在使用dom4j来读写XML,特别值得一提的是连Sun的JAXM也在用dom4j。这是必须使用的jar包,Hibernate用它来读写配置文件。 · odmg.jar: ODMG是一个ORM的规范,Hibernate实现了ODMG规范,这是一个核心的库,必须使用的jar包。 · commons-collections.jar: Apache Commons包中的一个,包含了一些Apache开发的集合类,功能比java.util.*强大。必须使用的jar包。 · commons-beanutils.jar: Apache Commons包中的一个,包含了一些Bean工具类类。必须使用的jar包。 · commons-lang.jar: Apache Commons包中的一个,包含了一些数据类型工具类,是java.lang.*的扩展。必须使用的jar包。 · commons-logging.jar: Apache Commons包中的一个,包含了日志功能,必须使用的jar包。这个包本身包含了一个Simple Logger,但是功能很弱。在运行的时候它会先在CLASSPATH找log4j,如果有,就使用log4j,如果没有,就找JDK1.4带的java.util.logging,如果也找不到就用Simple Logger。commons-logging.jar的出现是一个历史的的遗留的遗憾,当初Apache极力游说Sun把log4j加入JDK1.4,然而JDK1.4项目小组已经接近发布JDK1.4产品的时间了,因此拒绝了Apache的要求,使用自己的java.util.logging,这个包的功能比log4j差的很远,性能也一般。后来Apache就开发出来了commons-logging.jar用来兼容两个logger。因此用commons-logging.jar写的log程序,底层的Logger是可以切换的,你可以选择log4j,java.util.logging或者它自带的Simple Logger。不过我仍然强烈建议使用log4j,因为log4j性能很高,log输出信息时间几乎等于System.out,而处理一条log平均只需要5us。你可以在Hibernate的src目录下找到Hibernate已经为你准备好了的log4j的配置文件,你只需要到Apache 网站去下载log4j就可以了。commons-logging.jar也是必须的jar包。 使用Hibernate必须的jar包就是以上的这几个,剩下的都是可选的。 · ant.jar: Ant编译工具的jar包,用来编译Hibernate源代码的。如果你不准备修改和编译Hibernate源代码,那么就没有什么用,可选的jar包 · optional.jar: Ant的一个辅助包。 · c3p0.jar: C3PO是一个数据库连接池,Hibernate可以配置为使用C3PO连接池。如果你准备用这个连接池,就需要这个jar包。 · proxool.jar: 也是一个连接池,同上。 · commons-pool.jar, commons-dbcp.jar: DBCP数据库连接池,Apache的Jakarta组织开发的,Tomcat4的连接池也是DBCP。 实际上Hibernate自己也实现了一个非常非常简单的数据库连接池,加上上面3个,你实际上可以在Hibernate上选择4种不同的数据库连接池,选择哪一个看个人的偏好,不过DBCP可能更通用一些。另外强调一点,如果在EJB中使用Hibernate,一定要用App Server的连接池,不要用以上4种连接池,否则容器管理事务不起作用。 · connector.jar: JCA规范,如果你在App Server上把Hibernate配置为Connector的话,就需要这个jar。不过实际上一般App Server肯定会带上这个包,所以实际上是多余的包。 · jaas.jar: JAAS是用来进行权限验证的,已经包含在JDK1.4里面了。所以实际上是多余的包。 · jcs.jar: 如果你准备在Hibernate中使用JCS的话,那么必须包括它,否则就不用。 · jdbc2_0-stdext.jar: JDBC2.0的扩展包,一般来说数据库连接池会用上它。不过App Server都会带上,所以也是多余的。 · jta.jar: JTA规范,当Hibernate使用JTA的时候需要,不过App Server都会带上,所以也是多余的。 · junit.jar: Junit包,当你运行Hibernate自带的测试代码的时候需要,否则就不用。 · xalan.jar, xerces.jar, xml-apis.jar: Xerces是XML解析器,Xalan是格式化器,xml-apis实际上是JAXP。一般App Server都会带上,JDK1.4也包含了解析器,不过不是Xerces,是Crimson,效率比较差,不过Hibernate用XML只不过是读取配置文件,性能没什么紧要的,所以也是多余的。 6.JAVA多线程的问题以及处理【转】 Posted on 2009-12-03 17:43 火之光 阅读(603) 评论(1) 编辑 收藏 12.4多线程问题及处理 多线程编程为程序开发带来了很多的方便,但是也带来了一些问题,这些问题是在程序开发过程中必须进行处理的问题。 这些问题的核心是,如果多个线程同时访问一个资源,例如变量、文件等,时如何保证访问安全的问题。在多线程编程中,这种会被多个线程同时访问的资源叫做临界资源。 下面通过一个简单的示例,演示多个线程访问临界资源时产生的问题。在该示例中,启动了两个线程类DataThread的对象,该线程每隔200毫秒输出一次变量n的值,并将n的值减少1。变量n的值存储在模拟临界资源的Data类中,该示例的核心是两个线程类都使用同一个Data类的对象,这样Data类的这个对象就是一个临界资源了。示例代码如下: package syn1; /** *模拟临界资源的类 */ public class Data { public int n; public Data(){ n = 60; } } package syn1; /** *测试多线程访问时的问题 */ public class TestMulThread1 { public static void main(String[] args) { Data data = new Data(); DataThread d1 = new DataThread(data,"线程1"); DataThread d2 = new DataThread(data,"线程2"); } } package syn1; /** *访问数据的线程 */ public class DataThread extends Thread { Data data; String name; public DataThread(Data data,String name){ this.data = data; this.name = name; start(); } public void run(){ try{ for(int i = 0;i < 10;i++){ System.out.println(name + ":" + data.n); data.n--; Thread.sleep(200); } }catch(Exception e){} } } 在运行时,因为不同情况下该程序的运行结果会出现不同,该程序的一种执行结果为: 线程1:60 线程2:60 线程2:58 线程1:58 线程2:56 线程1:56 线程2:54 线程1:54 线程2:52 线程1:52 线程2:50 线程1:50 线程2:48 线程1:48 线程2:47 线程1:46 线程2:44 线程1:44 线程2:42 线程1:42 从执行结果来看,第一次都输出60是可以理解的,因为线程在执行时首先输出变量的值,这个时候变量n的值还是初始值60,而后续的输出就比较麻烦了,在开始的时候两个变量保持一致的输出,而不是依次输出n的每个值的内容,而到将要结束时,线程2输出47这个中间数值。 出现这种结果的原因很简单:线程1改变了变量n的值以后,还没有来得及输出,这个变量n的值就被线程2给改变了,所以在输出时看的输出都是跳跃的,偶尔出现了连续。 出现这个问题也比较容易接受,因为最基本的多线程程序,系统只保证线程同时执行,至于哪个先执行,哪个后执行,或者执行中会出现一个线程执行到一半,就把CPU的执行权交给了另外一个线程,这样线程的执行顺序是随机的,不受控制的。所以会出现上面的结果。 这种结果在很多实际应用中是不能被接受的,例如银行的应用,两个人同时取一个账户的存款,一个使用存折、一个使用卡,这样访问账户的金额就会出现问题。或者是售票系统中,如果也这样就出现有人买到相同座位的票,而有些座位的票却未售出。 在多线程编程中,这个是一个典型的临界资源问题,解决这个问题最基本,最简单的思路就是使用同步关键字synchronized。 synchronized关键字是一个修饰符,可以修饰方法或代码块,其的作用就是,对于同一个对象(不是一个类的不同对象), 当多个线程都同时调用该方法或代码块时,必须依次执行,也就是说,如果两个或两个以上的线程同时执行该段代码时,如果一个线程已经开始执行该段代码,则另 外一个线程必须等待这个线程执行完这段代码才能开始执行。就和在银行的柜台办理业务一样,营业员就是这个对象,每个顾客就好比线程,当一个顾客开始办理 时,其它顾客都必须等待,及时这个正在办理的顾客在办理过程中接了一个电话 (类比于这个线程释放了占用CPU的时间,而处于阻塞状态),其它线程也只能等待。 使用synchronized关键字修改以后的上面的代码为: package syn2; /** *模拟临界资源的类 */ public class Data2 { public int n; public Data2(){ n = 60; } public synchronized void action(String name){ System.out.println(name + ":" + n); n--; } } package syn2; /** *测试多线程访问时的问题 */ public class TestMulThread2 { public static void main(String[] args) { Data2 data = new Data2(); Data2Thread d1 = new Data2Thread(data,"线程1"); Data2Thread d2 = new Data2Thread(data,"线程2"); } } package syn2; /** *访问数据的线程 */ public class Data2Thread extends Thread { Data2 data; String name; public Data2Thread(Data2 data,String name){ this.data = data; this.name = name; start(); } public void run(){ try{ for(int i = 0;i < 10;i++){ data.action(name); Thread.sleep(200); } }catch(Exception e){} } } 该示例代码的执行结果会出现不同,一种执行结果为: 线程1:60 线程2:59 线程2:58 线程1:57 线程2:56 线程1:55 线程2:54 线程1:53 线程2:52 线程1:51 线程2:50 线程1:49 线程1:48 线程2:47 线程2:46 线程1:45 线程2:44 线程1:43 线程2:42 线程1:41 在该示例中,将打印变量n的代码和变量n变化的代码组成一个专门的方法action,并且使用修饰符synchronized修改该方法,也就是说对于一个Data2的对象,无论多少个线程同时调用action方法时,只有一个线程完全执行完该方法以后,别的线程才能够执行该方法。这就相当于一个线程执行到该对象的synchronized方法时,就为这个对象加上了一把锁,锁住了这个对象,别的线程在调用该方法时,发现了这把锁以后就继续等待下去了。 如果这个例子还不能帮助你理解如何解决多线程的问题,那么下面再来看一个更加实际的例子 ——卫生间问题。 例 如火车上车厢的卫生间,为了简单,这里只模拟一个卫生间,这个卫生间会被多个人同时使用,在实际使用时,当一个人进入卫生间时则会把卫生间锁上,等出来时 打开门,下一个人进去把门锁上,如果有一个人在卫生间内部则别人的人发现门是锁的则只能在外面等待。从编程的角度来看,这里的每个人都可以看作是一个线程 对象,而这个卫生间对象由于被多个线程访问,则就是临界资源,在一个线程实际使用时,使用synchronized关键将临界资源锁定,当结束时,释放锁定。实现的代码如下: package syn3; /** *测试类 */ public class TestHuman { public static void main(String[] args) { Toilet t = new Toilet(); //卫生间对象 Human h1 = new Human("1",t); Human h2 = new Human("2",t); Human h3 = new Human("3",t); } } package syn3; /** *人线程类,演示互斥 */ public class Human extends Thread { Toilet t; String name; public Human(String name,Toilet t){ this.name = name; this.t = t; start(); //启动线程 } public void run(){ //进入卫生间 t.enter(name); } } package syn3; /** *卫生间,互斥的演示 */ public class Toilet { public synchronized void enter(String name){ System.out.println(name + "已进入!"); try{ Thread.sleep(2000); }catch(Exception e){} System.out.println(name + "离开!"); } } 该示例的执行结果为,不同次数下执行结果会有所不同: 1已进入! 1离开! 3已进入! 3离开! 2已进入! 2离开! 在该示例代码中,Toilet类表示卫生间类,Human类模拟人,是该示例中的线程类,TestHuman类是测试类,用于启动线程。在TestHuman中,首先创建一个Toilet类型的对象t,并将该对象传递到后续创建的线程对象中,这样后续的线程对象就使用同一个Toilet对象,该对象就成为了临界资源。下面创建了三个Human类型的线程对象,每个线程具有自己的名称name参数,模拟3个线程,在每个线程对象中,只是调用对象t中的enter方法,模拟进入卫生间的动作,在enter方法中,在进入时输出调用该方法的线程进入,然后延迟2秒,输出该线程离开,然后后续的一个线程进入,直到三个线程都完成 enter方法则程序结束。 在该示例中,同一个Toilet类的对象t的enter方法由于具有synchronized修饰符修饰,则在多个线程同时调用该方法时,如果一个线程进入到enter方法内部,则为对象t上锁,直到enter方法结束以后释放对该对象的锁定,通过这种方式实现无论多少个Human类型的线程,对于同一个对象t,任何时候只能有一个线程执行enter方法,这就是解决多线程问题的第一种思路——互斥的解决原理。 12.4.2同步 使用互斥解决多线程问题是一种简单有效的解决办法,但是由于该方法比较简单,所以只能解决一些基本的问题,对于复杂的问题就无法解决了。 解 决多线程问题的另外一种思路是同步。同步是另外一种解决问题的思路,结合前面卫生间的示例,互斥方式解决多线程的原理是,当一个人进入到卫生间内部时,别 的人只能在外部时刻等待,这样就相当于别的人虽然没有事情做,但是还是要占用别的人的时间,浪费系统的执行资源。而同步解决问题的原理是,如果一个人进入 到卫生间内部时,则别的人可以去睡觉,不占用系统资源,而当这个人从卫生间出来以后,把这个睡觉的人叫醒,则它就可以使用临界资源了。所以使用同步的思路 解决多线程问题更加有效,更加节约系统的资源。 在常见的多线程问题解决中,同步问题的典型示例是“生产者-消费者 ”模型,也就是生产者线程只负责生产,消费者线程只负责消费,在消费者发现无内容可消费时则睡觉。下面举一个比较实际的例子——生活费问题。 生 活费问题是这样的:学生每月都需要生活费,家长一次预存一段时间的生活费,家长和学生使用统一的一个帐号,在学生每次取帐号中一部分钱,直到帐号中没钱时 通知家长存钱,而家长看到帐户还有钱则不存钱,直到帐户没钱时才存钱。在这个例子中,这个帐号被学生和家长两个线程同时访问,则帐号就是临界资源,两个线 程是同时执行的,当每个线程发现不符合要求时则等待,并释放分配给自己的CPU执行时间,也就是不占用系统资源。实现该示例的代码为: package syn4; /** *测试类 */ public class TestAccount { public static void main(String[] args) { Accout a = new Accout(); StudentThread s = new StudentThread(a); GenearchThread g = new GenearchThread(a); } } package syn4; /** *模拟学生线程 */ public class StudentThread extends Thread { Accout a; public StudentThread(Accout a){ this.a = a; start(); } public void run(){ try{ while(true){ Thread.sleep(2000); a.getMoney(); //取钱 } }catch(Exception e){} } } package syn4; /** *家长线程 */ public class GenearchThread extends Thread { Accout a; public GenearchThread(Accout a){ this.a = a; start(); } public void run(){ try{ while(true){ Thread.sleep(12000); a.saveMoney(); //存钱 } }catch(Exception e){} } } package syn4; /** *银行账户 */ public class Accout { int money = 0; /** *取钱 *如果账户没钱则等待,否则取出所有钱提醒存钱 */ public synchronized void getMoney(){ System.out.println("准备取钱!"); try{ if(money == 0){ wait(); //等待 } //取所有钱 System.out.println("剩余:" + money); money -= 50; //提醒存钱 notify(); }catch(Exception e){} } /** *存钱 *如果有钱则等待,否则存入200提醒取钱 */ public synchronized void saveMoney(){ System.out.println("准备存钱!"); try{ if(money != 0){ wait(); //等待 } //取所有钱 money = 200; System.out.println("存入:" + money); //提醒存钱 notify(); }catch(Exception e){} } } 该程序的一部分执行结果为: 准备取钱! 准备存钱! 存入:200 剩余:200 准备取钱! 剩余:150 准备取钱! 剩余:100 准备取钱! 剩余:50 准备取钱! 准备存钱! 存入:200 剩余:200 准备取钱! 剩余:150 准备取钱! 剩余:100 准备取钱! 剩余:50 准备取钱! 在该示例代码中,TestAccount类是测试类,主要实现创建帐户Account类的对象,以及启动学生线程StudentThread和启动家长线程GenearchThread。在StudentThread线程中,执行的功能是每隔2秒中取一次钱,每次取50元。在GenearchThread线程中,执行的功能是每隔12秒存一次钱,每次存200。这样存款和取款之间不仅时间间隔存在差异,而且数量上也会出现交叉。而该示例中,最核心的代码是Account类的实现。 在Account类中,实现了同步控制功能,在该类中包含一个关键的属性money,该属性的作用是存储帐户金额。在介绍该类的实现前,首先介绍一下两个同步方法——wait和notify方法的使用,这两个方法都是Object 类中的方法,也就是说每个类都包含这两个方法,换句话说,就是Java天生就支持同步处理。这两个方法都只能在synchronized修饰的方法或语句块内部采用被调用。其中wait方法的作用是使调用该方法的线程休眠,也就是使该线程退出CPU的等待队列,处于冬眠状态,不执行动作,也不占用CPU排队的时间,notify方法的作用是唤醒一个任意该对象的线程,该线程当前处于休眠状态,至于唤醒的具体是那个则不保证。在Account类中,被StudentThread调用的getMoney方法的功能是判断当前金额是否是0,如果是则使StudentThread线程处于休眠状态,如果金额不是0,则取出50元,同时唤醒使用该帐户对象的其它一个线程,而被GenearchThread线程调用的saveMoney方法的功能是判断当前是否不为0,如果是则使GenearchThread线程处于休眠状态,如果金额是0,则存入200元,同时唤醒使用该帐户对象的其它一个线程。 如果还是不清楚,那就结合前面的程序执行结果来解释一下程序执行的过程:在程序开始执行时,学生线程和家长线程都启动起来,所以输出“准备取钱”和“准备存钱”,然后学生线程按照该线程run方法的逻辑执行,先延迟2秒,然后调用帐户对象a中的getMoney方法,但是由于初始情况下帐户对象a中的money数值为0,所以学生线程就休眠了。在学生线程执行的同时,家长线程也按照该线程的run方法的逻辑执行,先延迟12秒,然后调用帐户对象a中的saveMoney方法,由于帐户a对象中的money为零,条件不成立,所以执行存入200元,同时唤醒线程,由于使用对象a的线程现在只有学生线程,所以学生线程被唤醒,开始执行逻辑,取出50元,然后唤醒线程,由于当前没有线程处于休眠状态,所以没有线程被唤醒。同时家长线程继续执行,先延迟 12秒,这个时候学生线程执行了4次,耗时4X2秒=8秒,就取光了帐户中的钱,接着由于帐户为0则学生线程又休眠了,一直到家长线程延迟12秒结束以后,判断帐户为0,又存入了200元,程序继续执行下去。 在解决多线程问题是,互斥和同步都是解决问题的思路,如果需要形象的比较这两种方式的区别的话,就看一下下面的示例。一个比较忙的老总,桌子上有2部电话,在一部处于通话状态时,另一部响了,老总拿其这部电话说我在接电话,你等一下,而没有挂电话,这种处理的方式就是互斥。而如果老总拿其另一部电话说,我在接电话,等会我打给你,然后挂了电话,这种处理的方式就是同步。两者相比,互斥明显占用系统资源(浪费电话费,浪费别人的时间),而同步则是一种更加好的解决问题的思路。 12.4.3死锁 多线程编程在实际的网络程序开发中,在客户端程序实现中使用的比较简单,但是在服务器端程序实现中却不仅是大量使用,而且会出现比客户端更多的问题。 另 外一个容易在服务器端出现的多线程问题是——死锁。死锁指两个或两个以上的线程为了使用某个临界资源而无限制的等待下去。还是以前面卫生间的例子来说明死 锁,例如两个人都同时到达卫生间,而且两个人都比较礼貌,第一个人和第二个人说:你先吧,第二个人和第一个人说:你先吧。这两个人就这样一直在互相礼让, 谁也不进入,这种现象就是死锁。这里的两个人就好比是线程,而卫生间在这里就是临界资源,而由于这两个线程在一直谦让,谁也不使用临界资源。 死锁不仅使程序无法达到预期实现的功能,而且浪费系统的资源,所以在服务器端程序中危害比较大,在实际的服务器端程序开发中,需要注意避免死锁。 而死锁的检测比较麻烦,而且不一定每次都出现,这就需要在测试服务器端程序时,有足够的耐心,仔细观察程序执行时的性能检测,如果发现执行的性能显著降低,则很可能是发生了死锁,然后再具体的查找死锁出现的原因,并解决死锁的问题。 死锁出现的最本质原因还是逻辑处理不够严谨,在考虑时不是很周全,所以一般需要修改程序逻辑才能够很好的解决死锁。 12.4.4线程优先级 在日常生活中,例如火车售票窗口等经常可以看到“XXX优先”,那么多线程编程中每个线程是否也可以设置优先级呢? 在多线程编程中,支持为每个线程设置优先级。优先级高的线程在排队执行时会获得更多的CPU执行时间,得到更快的响应。在实际程序中,可以根据逻辑的需要,将需要得到及时处理的线程设置成较高的优先级,而把对时间要求不高的线程设置成比较低的优先级。 在Thread类中,总计规定了三个优先级,分别为: l MAX_PRIORITY——最高优先级 l NORM_PRIORITY——普通优先级,也是默认优先级 l MIN_PRIORITY——最低优先级 在前面创建的线程对象中,由于没有设置线程的优先级,则线程默认的优先级是 NORM_PRIORITY,在实际使用时,也可以根据需要使用Thread类中的setPriority方法设置线程的优先级,该方法的声明为: public final void setPriority(int newPriority) 假设t是一个初始化过的线程对象,需要设置t的优先级为最高,则实现的代码为: t. setPriority(Thread. MAX_PRIORITY); 这样,在该线程执行时将获得更多的执行机会,也就是优先执行。如果由于安全等原因,不允许设置线程的优先级,则会抛出SecurityException异常。 下面使用一个简单的输出数字的线程演示线程优先级的使用,实现的示例代码如下: package priority; /** *测试线程优先级 */ public class TestPriority { public static void main(String[] args) { PrintNumberThread p1 = new PrintNumberThread("高优先级"); PrintNumberThread p2 = new PrintNumberThread("普通优先级"); PrintNumberThread p3 = new PrintNumberThread("低优先级"); p1.setPriority(Thread.MAX_PRIORITY); p2.setPriority(Thread.NORM_PRIORITY); p3.setPriority(Thread.MIN_PRIORITY); p1.start(); p2.start(); p3.start(); } } package priority; /** *输出数字的线程 */ public class PrintNumberThread extends Thread { String name; public PrintNumberThread(String name){ this.name = name; } public void run(){ try{ for(int i = 0;i < 10;i++){ System.out.println(name + ":" + i); } }catch(Exception e){} } } 程序的一种执行结果为: 高优先级:0 高优先级:1 高优先级:2 普通优先级:0 高优先级:3 普通优先级:1 高优先级:4 普通优先级:2 高优先级:5 高优先级:6 高优先级:7 高优先级:8 高优先级:9 普通优先级:3 普通优先级:4 普通优先级:5 普通优先级:6 普通优先级:7 普通优先级:8 普通优先级:9 低优先级:0 低优先级:1 低优先级:2 低优先级:3 低优先级:4 低优先级:5 低优先级:6 低优先级:7 低优先级:8 低优先级:9 在该示例程序,PrintNumberThread线程实现的功能是输出数字,每次数字输出之间没有设置时间延迟,在测试类TestPriority中创建三个PrintNumberThread类型的线程对象,然后分别设置线程优先级是最高、普通和最低,接着启动线程执行程序。从执行结果可以看出高优先级的线程获得了更多的执行时间,首先执行完成,而低优先级的线程由于优先级较低,所以最后一个执行结束。 其实,对于线程优先级的管理主要由系统的线程调度实现,较高优先级的线程优先执行,所以可以通过设置线程的优先级影响线程的执行。 12.5总结 关于多线程的基础知识就介绍这么多,在本章中介绍了线程的概念、线程的实现方式以及使用多线程时会遇到的问题以及解决办法,而需要建立多线程的概念,也就是并发编程的概念还需要进行比较多的练习,理解多线程的概念并熟悉多线程的编程。 而关于多线程编程的高级知识,如线程组等则可以在熟悉了线程的基本概念以后再进行更加深入的学习。 12.6多线程练习 1、分别使用多线程的3种实现方法,实现一个打印奇数的线程 2、分别使用多线程的3种实现方法,实现一个打印1-10000之间素数(质数)的线程 3、在练习1、练习2的基础上,加入控制台输入,当线程执行时,输入quit或exit结束线程和程序的执行。 4、实现两个线程,一个打印奇数,一个打印偶数,每个线程的延迟时间不一样,实现奇数和偶数的交替打印。 5、模拟火车票联网售票系统:多个线程同时出票,保证每张出票的编号连续且不重复。 7.JAVA读取控制台的输入【转】 Posted on 2009-12-03 17:23 火之光 阅读(472) 评论(0) 编辑 收藏 前面介绍了使用IO类实现文件读写的示例,其实在很多地方还需要使用到IO 类,这里再以读取控制台输入为例子来介绍IO类的使用。 控制台(Console)指无图形界面的程序,运行时显示或输入数据的位置,前面的介绍中可以使用System.out.println将需要输出的内容显示到控制台,本部分将介绍如何接受用户在控制台中的输入。 使用控制台输入是用户在程序运行时和程序进行交互的一种基础手段,这种手段是Windows操作系统出现以前,操作系统位于DOS时代时,用户和程序交互的主要手段。当然,现在这种交互的方式已经被图形界面(GUI)程序取代了。 在读取控制台操作中,操作系统在用户在控制台输入内容,并按回车键提交以后,将用户提交的内容传递给Java运行时系统,Java运行时系统将用户输入的信息构造成一个输入流对象——System.in,在程序员读取控制台输入时,只需要从该流中读取数据即可。至于构造流System.in的过程对于程序员来说是透明的。 查阅JDK API可以发现,System类中的静态属性in是InputStream类型的对象,可以按照输入流的读取方法读取即可。 下面的示例代码实现了输入“回显”的功能,即将用户输入的内容重新显示到控制台,示例代码如下: /** *读取控制台输入,并将输入的内容显示到控制台 */ public class ReadConsole1 { public static void main(String[] args) { try{ //提示信息 System.out.println("请输入:"); //数组缓冲 byte[] b = new byte[1024]; //读取数据 int n = System.in.read(b); //转换为字符串 String s = new String(b,0,n); //回显内容 System.out.println("输入内容为:" + s); }catch(Exception e){} } } 在该示例代码中,从System.in中读取出用户的输入,然后将用户输入的内容转换为字符串s,然后输出该字符串的内容即可。 下面实现一个简单的逻辑,功能为:回显用户在控制台输入的内容,当用户输入quit时程序运行结束。实现的代码如下: /** *读取控制台输入 *循环回显内容,当输入quit时退出程序 */ public class ReadConsole2 { public static void main(String[] args) { //数组缓冲 byte[] b = new byte[1024]; //有效数据个数 int n = 0; try{ while(true){ //提示信息 System.out.println("请输入:"); //读取数据 n = System.in.read(b); //转换为字符串 String s = new String(b,0,n - 2); //判断是否是quit if(s.equalsIgnoreCase("quit")){ break; //结束循环 } //回显内容 System.out.println("输入内容为:" + s); } }catch(Exception e){} } } 在该示例代码中,加入了一个while循环,使得用户的输入可以进行多次,在用户输入时,送入输入流的内容除了用户输入的内容以外,还包含”\r\n”这两个字符,所以在将输入的内容和quit比较时,去掉读出的最后2个字符,将剩余的内容转换为字符串。 最后是一个简单的《掷骰子》的控制台小游戏,在该游戏中,玩家初始拥有1000的金钱,每次输入押大还是押小,以及下注金额,随机3个骰子的点数,如果3个骰子的总点数小于等于9,则开小,否则开大,然后判断玩家是否押对,如果未押对则扣除下注金额,如果押对则奖励和玩家下注金额相同的金钱。该程序的示例代码如下: /** *掷骰子游戏实现 */ public class DiceGame { public static void main(String[] args) { int money = 1000; //初始金钱数量 int diceNum = 0; //掷出的骰子数值和 int type = 0; //玩家押的大小 int cMoney = 0; //当前下注金额 boolean success; //胜负 //游戏过程 while (true) { //输入大小 System.out.println("请押大小(1代表大,2代表小):"); type = readKeyboard(); //校验 if (!checkType(type)) { System.out.println("输入非法,请重新输入!"); continue; } //输入下注金额 while(true){ System.out.println("你当前的金钱数量是" + money + " 请下注:"); cMoney = readKeyboard(); //校验 if (!checkCMoney(money,cMoney)) { System.out.println("输入非法,请重新输入!"); continue; }else{ break; } } //掷骰子 diceNum = doDice(); //判断胜负 success = isSuccess(type,diceNum); //金钱变化 money = changeMoney(money,success,cMoney); //游戏结束 if(isEnd(money)){ System.out.println("你输了, bye!"); break; } } } /** *读取用户输入 * @return玩家输入的整数,如果格式非法则返回0 */ public static int readKeyboard() { try { //缓冲区数组 byte[] b = new byte[1024]; //读取用户输入到数组b中, //读取的字节数量为n int n = System.in.read(b); //转换为整数 String s = new String(b, 0, n - 2); int num = Integer.parseInt(s); return num; } catch (Exception e) {} return 0; } /** *押的类型校验 * @param type 类型 * @return true代表符合要求,false代表不符合 */ public static boolean checkType(int type) { if (type == 1 || type == 2) { return true; } else { return false; } } /** *校验下注金额是否合法 * @param money 玩家金钱数 * @param cMoney 下注金额 * @return true代表符合要求,false代表不符合要求 */ public static boolean checkCMoney(int money, int cMoney) { if (cMoney <= 0) { return false; } else if (cMoney <= money) { return true; } else { return false; } } /** *掷骰子 * @return骰子的数值之和 */ public static int doDice() { int n = (int) (Math.random() * 6) + 1; int n1 = (int) (Math.random() * 6) + 1; int n2 = (int) (Math.random() * 6) + 1; //输出随机结果 System.out.println("庄家开:" + n + " " + n1 + " " + n2); return n + n1 + n2; } /** *胜负判断 * @param type 用户输入类型 * @param diceNum 骰子点数 * @return true代表赢,false代表输 */ public static boolean isSuccess(int type, int diceNum) { //计算庄家类型 int bankerType = 0; if (diceNum <= 9) { bankerType = 2; System.out.println("庄家开小!"); } else { bankerType = 1; System.out.println("庄家开大!"); } if (bankerType == type) { //赢 return true; } else { //输 return false; } } /** *金钱变化 * @param money用户钱数 * @param success胜负 * @param cMoney下注金额 * @return变化以后的金钱 */ public static int changeMoney(int money, boolean success, int cMoney) { if (success) { money += cMoney; } else { money -= cMoney; } System.out.println("剩余金额:" + money); return money; } /** *判断游戏是否结束 * @param money玩家金钱 * @return true代表结束 */ public static boolean isEnd(int money) { return money <= 0; } } 8.JAVA多线程的问题以及处理【转】 Posted on 2009-12-03 17:43 火之光 阅读(606) 评论(1) 编辑 收藏 12.4多线程问题及处理 多线程编程为程序开发带来了很多的方便,但是也带来了一些问题,这些问题是在程序开发过程中必须进行处理的问题。 这些问题的核心是,如果多个线程同时访问一个资源,例如变量、文件等,时如何保证访问安全的问题。在多线程编程中,这种会被多个线程同时访问的资源叫做临界资源。 下面通过一个简单的示例,演示多个线程访问临界资源时产生的问题。在该示例中,启动了两个线程类DataThread的对象,该线程每隔200毫秒输出一次变量n的值,并将n的值减少1。变量n的值存储在模拟临界资源的Data类中,该示例的核心是两个线程类都使用同一个Data类的对象,这样Data类的这个对象就是一个临界资源了。示例代码如下: package syn1; /** *模拟临界资源的类 */ public class Data { public int n; public Data(){ n = 60; } } package syn1; /** *测试多线程访问时的问题 */ public class TestMulThread1 { public static void main(String[] args) { Data data = new Data(); DataThread d1 = new DataThread(data,"线程1"); DataThread d2 = new DataThread(data,"线程2"); } } package syn1; /** *访问数据的线程 */ public class DataThread extends Thread { Data data; String name; public DataThread(Data data,String name){ this.data = data; this.name = name; start(); } public void run(){ try{ for(int i = 0;i < 10;i++){ System.out.println(name + ":" + data.n); data.n--; Thread.sleep(200); } }catch(Exception e){} } } 在运行时,因为不同情况下该程序的运行结果会出现不同,该程序的一种执行结果为: 线程1:60 线程2:60 线程2:58 线程1:58 线程2:56 线程1:56 线程2:54 线程1:54 线程2:52 线程1:52 线程2:50 线程1:50 线程2:48 线程1:48 线程2:47 线程1:46 线程2:44 线程1:44 线程2:42 线程1:42 从执行结果来看,第一次都输出60是可以理解的,因为线程在执行时首先输出变量的值,这个时候变量n的值还是初始值60,而后续的输出就比较麻烦了,在开始的时候两个变量保持一致的输出,而不是依次输出n的每个值的内容,而到将要结束时,线程2输出47这个中间数值。 出现这种结果的原因很简单:线程1改变了变量n的值以后,还没有来得及输出,这个变量n的值就被线程2给改变了,所以在输出时看的输出都是跳跃的,偶尔出现了连续。 出现这个问题也比较容易接受,因为最基本的多线程程序,系统只保证线程同时执行,至于哪个先执行,哪个后执行,或者执行中会出现一个线程执行到一半,就把CPU的执行权交给了另外一个线程,这样线程的执行顺序是随机的,不受控制的。所以会出现上面的结果。 这种结果在很多实际应用中是不能被接受的,例如银行的应用,两个人同时取一个账户的存款,一个使用存折、一个使用卡,这样访问账户的金额就会出现问题。或者是售票系统中,如果也这样就出现有人买到相同座位的票,而有些座位的票却未售出。 在多线程编程中,这个是一个典型的临界资源问题,解决这个问题最基本,最简单的思路就是使用同步关键字synchronized。 synchronized关键字是一个修饰符,可以修饰方法或代码块,其的作用就是,对于同一个对象(不是一个类的不同对象), 当多个线程都同时调用该方法或代码块时,必须依次执行,也就是说,如果两个或两个以上的线程同时执行该段代码时,如果一个线程已经开始执行该段代码,则另 外一个线程必须等待这个线程执行完这段代码才能开始执行。就和在银行的柜台办理业务一样,营业员就是这个对象,每个顾客就好比线程,当一个顾客开始办理 时,其它顾客都必须等待,及时这个正在办理的顾客在办理过程中接了一个电话 (类比于这个线程释放了占用CPU的时间,而处于阻塞状态),其它线程也只能等待。 使用synchronized关键字修改以后的上面的代码为: package syn2; /** *模拟临界资源的类 */ public class Data2 { public int n; public Data2(){ n = 60; } public synchronized void action(String name){ System.out.println(name + ":" + n); n--; } } package syn2; /** *测试多线程访问时的问题 */ public class TestMulThread2 { public static void main(String[] args) { Data2 data = new Data2(); Data2Thread d1 = new Data2Thread(data,"线程1"); Data2Thread d2 = new Data2Thread(data,"线程2"); } } package syn2; /** *访问数据的线程 */ public class Data2Thread extends Thread { Data2 data; String name; public Data2Thread(Data2 data,String name){ this.data = data; this.name = name; start(); } public void run(){ try{ for(int i = 0;i < 10;i++){ data.action(name); Thread.sleep(200); } }catch(Exception e){} } } 该示例代码的执行结果会出现不同,一种执行结果为: 线程1:60 线程2:59 线程2:58 线程1:57 线程2:56 线程1:55 线程2:54 线程1:53 线程2:52 线程1:51 线程2:50 线程1:49 线程1:48 线程2:47 线程2:46 线程1:45 线程2:44 线程1:43 线程2:42 线程1:41 在该示例中,将打印变量n的代码和变量n变化的代码组成一个专门的方法action,并且使用修饰符synchronized修改该方法,也就是说对于一个Data2的对象,无论多少个线程同时调用action方法时,只有一个线程完全执行完该方法以后,别的线程才能够执行该方法。这就相当于一个线程执行到该对象的synchronized方法时,就为这个对象加上了一把锁,锁住了这个对象,别的线程在调用该方法时,发现了这把锁以后就继续等待下去了。 如果这个例子还不能帮助你理解如何解决多线程的问题,那么下面再来看一个更加实际的例子——卫生间问题。 例 如火车上车厢的卫生间,为了简单,这里只模拟一个卫生间,这个卫生间会被多个人同时使用,在实际使用时,当一个人进入卫生间时则会把卫生间锁上,等出来时 打开门,下一个人进去把门锁上,如果有一个人在卫生间内部则别人的人发现门是锁的则只能在外面等待。从编程的角度来看,这里的每个人都可以看作是一个线程 对象,而这个卫生间对象由于被多个线程访问,则就是临界资源,在一个线程实际使用时,使用synchronized关键将临界资源锁定,当结束时,释放锁定。实现的代码如下: package syn3; /** *测试类 */ public class TestHuman { public static void main(String[] args) { Toilet t = new Toilet(); //卫生间对象 Human h1 = new Human("1",t); Human h2 = new Human("2",t); Human h3 = new Human("3",t); } } package syn3; /** *人线程类,演示互斥 */ public class Human extends Thread { Toilet t; String name; public Human(String name,Toilet t){ this.name = name; this.t = t; start(); //启动线程 } public void run(){ //进入卫生间 t.enter(name); } } package syn3; /** *卫生间,互斥的演示 */ public class Toilet { public synchronized void enter(String name){ System.out.println(name + "已进入!"); try{ Thread.sleep(2000); }catch(Exception e){} System.out.println(name + "离开!"); } } 该示例的执行结果为,不同次数下执行结果会有所不同: 1已进入! 1离开! 3已进入! 3离开! 2已进入! 2离开! 在该示例代码中,Toilet类表示卫生间类,Human类模拟人,是该示例中的线程类,TestHuman类是测试类,用于启动线程。在TestHuman中,首先创建一个Toilet类型的对象t,并将该对象传递到后续创建的线程对象中,这样后续的线程对象就使用同一个Toilet对象,该对象就成为了临界资源。下面创建了三个Human类型的线程对象,每个线程具有自己的名称name参数,模拟3个线程,在每个线程对象中,只是调用对象t中的enter方法,模拟进入卫生间的动作,在enter方法中,在进入时输出调用该方法的线程进入,然后延迟2秒,输出该线程离开,然后后续的一个线程进入,直到三个线程都完成enter方法则程序结束。 在该示例中,同一个Toilet类的对象t的enter方法由于具有synchronized修饰符修饰,则在多个线程同时调用该方法时,如果一个线程进入到enter方法内部,则为对象t上锁,直到enter方法结束以后释放对该对象的锁定,通过这种方式实现无论多少个Human类型的线程,对于同一个对象t,任何时候只能有一个线程执行enter方法,这就是解决多线程问题的第一种思路——互斥的解决原理。 12.4.2同步 使用互斥解决多线程问题是一种简单有效的解决办法,但是由于该方法比较简单,所以只能解决一些基本的问题,对于复杂的问题就无法解决了。 解 决多线程问题的另外一种思路是同步。同步是另外一种解决问题的思路,结合前面卫生间的示例,互斥方式解决多线程的原理是,当一个人进入到卫生间内部时,别 的人只能在外部时刻等待,这样就相当于别的人虽然没有事情做,但是还是要占用别的人的时间,浪费系统的执行资源。而同步解决问题的原理是,如果一个人进入 到卫生间内部时,则别的人可以去睡觉,不占用系统资源,而当这个人从卫生间出来以后,把这个睡觉的人叫醒,则它就可以使用临界资源了。所以使用同步的思路 解决多线程问题更加有效,更加节约系统的资源。 在常见的多线程问题解决中,同步问题的典型示例是“生产者-消费者”模型,也就是生产者线程只负责生产,消费者线程只负责消费,在消费者发现无内容可消费时则睡觉。下面举一个比较实际的例子——生活费问题。 生 活费问题是这样的:学生每月都需要生活费,家长一次预存一段时间的生活费,家长和学生使用统一的一个帐号,在学生每次取帐号中一部分钱,直到帐号中没钱时 通知家长存钱,而家长看到帐户还有钱则不存钱,直到帐户没钱时才存钱。在这个例子中,这个帐号被学生和家长两个线程同时访问,则帐号就是临界资源,两个线 程是同时执行的,当每个线程发现不符合要求时则等待,并释放分配给自己的 CPU执行时间,也就是不占用系统资源。实现该示例的代码为: package syn4; /** *测试类 */ public class TestAccount { public static void main(String[] args) { Accout a = new Accout(); StudentThread s = new StudentThread(a); GenearchThread g = new GenearchThread(a); } } package syn4; /** *模拟学生线程 */ public class StudentThread extends Thread { Accout a; public StudentThread(Accout a){ this.a = a; start(); } public void run(){ try{ while(true){ Thread.sleep(2000); a.getMoney(); //取钱 } }catch(Exception e){} } } package syn4; /** *家长线程 */ public class GenearchThread extends Thread { Accout a; public GenearchThread(Accout a){ this.a = a; start(); } public void run(){ try{ while(true){ Thread.sleep(12000); a.saveMoney(); //存钱 } }catch(Exception e){} } } package syn4; /** *银行账户 */ public class Accout { int money = 0; /** *取钱 *如果账户没钱则等待,否则取出所有钱提醒存钱 */ public synchronized void getMoney(){ System.out.println("准备取钱!"); try{ if(money == 0){ wait(); //等待 } //取所有钱 System.out.println("剩余:" + money); money -= 50; //提醒存钱 notify(); }catch(Exception e){} } /** *存钱 *如果有钱则等待,否则存入200提醒取钱 */ public synchronized void saveMoney(){ System.out.println("准备存钱!"); try{ if(money != 0){ wait(); //等待 } //取所有钱 money = 200; System.out.println("存入:" + money); //提醒存钱 notify(); }catch(Exception e){} } } 该程序的一部分执行结果为: 准备取钱! 准备存钱! 存入:200 剩余:200 准备取钱! 剩余:150 准备取钱! 剩余:100 准备取钱! 剩余:50 准备取钱! 准备存钱! 存入:200 剩余:200 准备取钱! 剩余:150 准备取钱! 剩余:100 准备取钱! 剩余:50 准备取钱! 在该示例代码中,TestAccount类是测试类,主要实现创建帐户Account类的对象,以及启动学生线程StudentThread和启动家长线程GenearchThread。在StudentThread线程中,执行的功能是每隔2秒中取一次钱,每次取50元。在GenearchThread线程中,执行的功能是每隔12秒存一次钱,每次存200。这样存款和取款之间不仅时间间隔存在差异,而且数量上也会出现交叉。而该示例中,最核心的代码是Account类的实现。 在Account类中,实现了同步控制功能,在该类中包含一个关键的属性money,该属性的作用是存储帐户金额。在介绍该类的实现前,首先介绍一下两个同步方法——wait和notify方法的使用,这两个方法都是Object类中的方法,也就是说每个类都包含这两个方法,换句话说,就是Java天生就支持同步处理。这两个方法都只能在synchronized修饰的方法或语句块内部采用被调用。其中wait方法的作用是使调用该方法的线程休眠,也就是使该线程退出CPU的等待队列,处于冬眠状态,不执行动作,也不占用CPU排队的时间,notify方法的作用是唤醒一个任意该对象的线程,该线程当前处于休眠状态,至于唤醒的具体是那个则不保证。在Account类中,被StudentThread调用的getMoney方法的功能是判断当前金额是否是0,如果是则使 StudentThread线程处于休眠状态,如果金额不是0,则取出50元,同时唤醒使用该帐户对象的其它一个线程,而被GenearchThread线程调用的saveMoney方法的功能是判断当前是否不为0,如果是则使GenearchThread线程处于休眠状态,如果金额是0,则存入200元,同时唤醒使用该帐户对象的其它一个线程。 如果还是不清楚,那就结合前面的程序执行结果来解释一下程序执行的过程:在程序开始执行时,学生线程和家长线程都启动起来,所以输出“准备取钱”和“准备存钱”,然后学生线程按照该线程run方法的逻辑执行,先延迟2秒,然后调用帐户对象a中的getMoney方法,但是由于初始情况下帐户对象a中的money数值为0,所以学生线程就休眠了。在学生线程执行的同时,家长线程也按照该线程的run方法的逻辑执行,先延迟12秒,然后调用帐户对象a中的saveMoney方法,由于帐户a对象中的money为零,条件不成立,所以执行存入200元,同时唤醒线程,由于使用对象a的线程现在只有学生线程,所以学生线程被唤醒,开始执行逻辑,取出50元,然后唤醒线程,由于当前没有线程处于休眠状态,所以没有线程被唤醒。同时家长线程继续执行,先延迟12秒,这个时候学生线程执行了4次,耗时4X2秒=8秒,就取光了帐户中的钱,接着由于帐户为0则学生线程又休眠了,一直到家长线程延迟12秒结束以后,判断帐户为0,又存入了200元,程序继续执行下去。 在解决多线程问题是,互斥和同步都是解决问题的思路,如果需要形象的比较这两种方式的区别的话,就看一下下面的示例。一个比较忙的老总,桌子上有2部电话,在一部处于通话状态时,另一部响了,老总拿其这部电话说我在接电话,你等一下,而没有挂电话,这种处理的方式就是互斥。而如果老总拿其另一部电话说,我在接电话,等会我打给你,然后挂了电话,这种处理的方式就是同步。两者相比,互斥明显占用系统资源 (浪费电话费,浪费别人的时间),而同步则是一种更加好的解决问题的思路。 12.4.3死锁 多线程编程在实际的网络程序开发中,在客户端程序实现中使用的比较简单,但是在服务器端程序实现中却不仅是大量使用,而且会出现比客户端更多的问题。 另 外一个容易在服务器端出现的多线程问题是——死锁。死锁指两个或两个以上的线程为了使用某个临界资源而无限制的等待下去。还是以前面卫生间的例子来说明死 锁,例如两个人都同时到达卫生间,而且两个人都比较礼貌,第一个人和第二个人说:你先吧,第二个人和第一个人说:你先吧。这两个人就这样一直在互相礼让, 谁也不进入,这种现象就是死锁。这里的两个人就好比是线程,而卫生间在这里就是临界资源,而由于这两个线程在一直谦让,谁也不使用临界资源。 死锁不仅使程序无法达到预期实现的功能,而且浪费系统的资源,所以在服务器端程序中危害比较大,在实际的服务器端程序开发中,需要注意避免死锁。 而死锁的检测比较麻烦,而且不一定每次都出现,这就需要在测试服务器端程序时,有足够的耐心,仔细观察程序执行时的性能检测,如果发现执行的性能显著降低,则很可能是发生了死锁,然后再具体的查找死锁出现的原因,并解决死锁的问题。 死锁出现的最本质原因还是逻辑处理不够严谨,在考虑时不是很周全,所以一般需要修改程序逻辑才能够很好的解决死锁。 12.4.4线程优先级 在日常生活中,例如火车售票窗口等经常可以看到“XXX优先”,那么多线程编程中每个线程是否也可以设置优先级呢? 在多线程编程中,支持为每个线程设置优先级。优先级高的线程在排队执行时会获得更多的CPU执行时间,得到更快的响应。在实际程序中,可以根据逻辑的需要,将需要得到及时处理的线程设置成较高的优先级,而把对时间要求不高的线程设置成比较低的优先级。 在Thread类中,总计规定了三个优先级,分别为: l MAX_PRIORITY——最高优先级 l NORM_PRIORITY——普通优先级,也是默认优先级 l MIN_PRIORITY——最低优先级 在前面创建的线程对象中,由于没有设置线程的优先级,则线程默认的优先级是NORM_PRIORITY,在实际使用时,也可以根据需要使用Thread类中的setPriority方法设置线程的优先级,该方法的声明为: public final void setPriority(int newPriority) 假设t是一个初始化过的线程对象,需要设置t的优先级为最高,则实现的代码为: t. setPriority(Thread. MAX_PRIORITY); 这样,在该线程执行时将获得更多的执行机会,也就是优先执行。如果由于安全等原因,不允许设置线程的优先级,则会抛出 SecurityException异常。 下面使用一个简单的输出数字的线程演示线程优先级的使用,实现的示例代码如下: package priority; /** *测试线程优先级 */ public class TestPriority { public static void main(String[] args) { PrintNumberThread p1 = new PrintNumberThread("高优先级"); PrintNumberThread p2 = new PrintNumberThread("普通优先级"); PrintNumberThread p3 = new PrintNumberThread("低优先级"); p1.setPriority(Thread.MAX_PRIORITY); p2.setPriority(Thread.NORM_PRIORITY); p3.setPriority(Thread.MIN_PRIORITY); p1.start(); p2.start(); p3.start(); } } package priority; /** *输出数字的线程 */ public class PrintNumberThread extends Thread { String name; public PrintNumberThread(String name){ this.name = name; } public void run(){ try{ for(int i = 0;i < 10;i++){ System.out.println(name + ":" + i); } }catch(Exception e){} } } 程序的一种执行结果为: 高优先级:0 高优先级:1 高优先级:2 普通优先级:0 高优先级:3 普通优先级:1 高优先级:4 普通优先级:2 高优先级:5 高优先级:6 高优先级:7 高优先级:8 高优先级:9 普通优先级:3 普通优先级:4 普通优先级:5 普通优先级:6 普通优先级:7 普通优先级:8 普通优先级:9 低优先级:0 低优先级:1 低优先级:2 低优先级:3 低优先级:4 低优先级:5 低优先级:6 低优先级:7 低优先级:8 低优先级:9 在该示例程序,PrintNumberThread线程实现的功能是输出数字,每次数字输出之间没有设置时间延迟,在测试类TestPriority中创建三个PrintNumberThread类型的线程对象,然后分别设置线程优先级是最高、普通和最低,接着启动线程执行程序。从执行结果可以看出高优先级的线程获得了更多的执行时间,首先执行完成,而低优先级的线程由于优先级较低,所以最后一个执行结束。 其实,对于线程优先级的管理主要由系统的线程调度实现,较高优先级的线程优先执行,所以可以通过设置线程的优先级影响线程的执行。 12.5总结 关于多线程的基础知识就介绍这么多,在本章中介绍了线程的概念、线程的实现方式以及使用多线程时会遇到的问题以及解决办法,而需要建立多线程的概念,也就是并发编程的概念还需要进行比较多的练习,理解多线程的概念并熟悉多线程的编程。 而关于多线程编程的高级知识,如线程组等则可以在熟悉了线程的基本概念以后再进行更加深入的学习。 12.6多线程练习 1、分别使用多线程的3种实现方法,实现一个打印奇数的线程 2、分别使用多线程的3种实现方法,实现一个打印1-10000之间素数(质数)的线程 3、在练习1、练习2的基础上,加入控制台输入,当线程执行时,输入quit或exit结束线程和程序的执行。 4、实现两个线程,一个打印奇数,一个打印偶数,每个线程的延迟时间不一样,实现奇数和偶数的交替打印。 5、模拟火车票联网售票系统:多个线程同时出票,保证每张出票的编号连续且不重复。 9.JAVA的文件操作【转】 Posted on 2009-12-03 17:20 火之光 阅读(2766) 评论(0) 编辑 收藏 11.3 I/O类使用 由于在IO操作中,需要使用的数据源有很多,作为一个IO技术的初学者,从读写文件开始学习IO技术是一个比较好的选择。因为文件是一种常见的数据源,而且读写文件也是程序员进行IO编程的一个基本能力。本章IO类的使用就从读写文件开始。 11.3.1文件操作 文件(File)是 最常见的数据源之一,在程序中经常需要将数据存储到文件中,例如图片文件、声音文件等数据文件,也经常需要根据需要从指定的文件中进行数据的读取。当然, 在实际使用时,文件都包含一个的格式,这个格式需要程序员根据需要进行设计,读取已有的文件时也需要熟悉对应的文件格式,才能把数据从文件中正确的读取出 来。 文件的存储介质有很多,例如硬盘、光盘和U盘等,由于IO类设计时,从数据源转换为流对象的操作由API实现了,所以存储介质的不同对于程序员来说是透明的,和实际编写代码无关。 11.3.1.1文件的概念 文件是计算机中一种基本的数据存储形式,在实际存储数据时,如果对于数据的读写速度要求不是很高,存储的数据量不是很大时,使用文件作为一种持久数据存储的方式是比较好的选择。 存储在文件内部的数据和内存中的数据不同,存储在文件中的数据是一种“持久存储”,也就是当程序退出或计算机关机以后,数据还是存在的,而内存内部的数据在程序退出或计算机关机以后,数据就丢失了。 在不同的存储介质中,文件中的数据都是以一定的顺序依次存储起来,在实际读取时由硬件以及操作系统完成对于数据的控制,保证程序读取到的数据和存储的顺序保持一致。 每个文件以一个文件路径和文件名称进行表示,在需要访问该文件的时,只需要知道该文件的路径以及文件的全名即可。在不同的操作系统环境下,文件路径的表示形式是不一样的,例如在Windows操作系统中一般的表示形式为C:\windows\system,而Unix上的表示形式为/user/my。所以如果需要让 Java程序能够在不同的操作系统下运行,书写文件路径时还需要比较注意。 11.3.1.1.1绝对路径和相对路径 绝对路径是指书写文件的完整路径,例如d:\java\Hello.java,该路径中包含文件的完整路径d:\java以及文件的全名Hello.java。使用该路径可以唯一的找到一个文件,不会产生歧义。但是使用绝对路径在表示文件时,受到的限制很大,且不能在不同的操作系统下运行,因为不同操作系统下绝对路径的表达形式存在不同。 相对路径是指书写文件的部分路径,例如\test\Hello.java,该路径中只包含文件的部分路径\test和文件的全名Hello.java,部分路径是指当前路径下的子路径,例如当前程序在d:\abc下运行,则该文件的完整路径就是d:\abc\test。使用这种形式,可以更加通用的代表文件的位置,使得文件路径产生一定的灵活性。 在Eclipse项目中运行程序时,当前路径是项目的根目录,例如工作空间存储在d:\javaproject,当前项目名称是Test,则当前路径是:d:\javaproject\Test。在控制台下面运行程序时,当前路径是class文件所在的目录,如果class文件包含包名,则以该class文件最顶层的包名作为当前路径。 另外在Java语言的代码内部书写文件路径时,需要注意大小写,大小写需要保持一致,路径中的文件夹名称区分大小写。由于’\’是Java语言中的特殊字符,所以在代码内部书写文件路径时,例如代表 “c:\test\java\Hello.java”时,需要书写成“c:\\test\\java\\Hello.java”或“c:/test/java/Hello.java”,这些都需要在代码中注意。 11.3.1.1.2文件名称 文件名称一般采用“文件名.后缀名”的形式进行命名,其中“文件名”用来表示文件的作用,而使用后缀名来表示文件的类型,这是当前操作系统中常见的一种形式,例如“readme.txt”文件,其中readme代表该文件时说明文件,而txt后缀名代表文件时文本文件类型,在操作系统中,还会自动将特定格式的后缀名和对应的程序关联,在双击该文件时使用特定的程序打开。 其实在文件名称只是一个标示,和实际存储的文件内容没有必然的联系,只是使用这种方式方便文件的使用。在程序中需要存储数据时,如果自己设计了特定的文件格式,则可以自定义文件的后缀名,来标示自己的文件类型。 和文件路径一样,在Java代码内部书写文件名称时也区分大小写,文件名称的大小写必须和操作系统中的大小写保持一致。 另外,在书写文件名称时不要忘记书写文件的后缀名。 11.3.1.2 File类 为了很方便的代表文件的概念,以及存储一些对于文件的基本操作,在java.io包中设计了一个专门的类——File类。 在File类中包含了大部分和文件操作的功能方法,该类的对象可以代表一个具体的文件或文件夹,所以以前曾有人建议将该类的类名修改成FilePath,因为该类也可以代表一个文件夹,更准确的说是可以代表一个文件路径。 下面介绍一下File类的基本使用。 1、File对象代表文件路径 File类的对象可以代表一个具体的文件路径,在实际代表时,可以使用绝对路径也可以使用相对路径。 下面是创建的文件对象示例。 public File(String pathname) 该示例中使用一个文件路径表示一个File类的对象,例如: File f1 = new File(“d:\\test\\1.txt”); File f2 = new File(“1.txt”); File f3 = new File(“e:\\abc”); 这里的f1和f2对象分别代表一个文件,f1是绝对路径,而f2是相对路径,f3则代表一个文件夹,文件夹也是文件路径的一种。 public File(String parent, String child) 也可以使用父路径和子路径结合,实现代表文件路径,例如: File f4 = new File(“d:\\test\\”,”1.txt”); 这样代表的文件路径是:d:\test\1.txt。 2、File类常用方法 File类中包含了很多获得文件或文件夹属性的方法,使用起来比较方便,下面将常见的方法介绍如下: a、createNewFile方法 public boolean createNewFile() throws IOException 该方法的作用是创建指定的文件。该方法只能用于创建文件,不能用于创建文件夹,且文件路径中包含的文件夹必须存在。 b、delect方法 public boolean delete() 该方法的作用是删除当前文件或文件夹。如果删除的是文件夹,则该文件夹必须为空。如果需要删除一个非空的文件夹,则需要首先删除该文件夹内部的每个文件和文件夹,然后在可以删除,这个需要书写一定的逻辑代码实现。 c、exists方法 public boolean exists() 该方法的作用是判断当前文件或文件夹是否存在。 d、getAbsolutePath方法 public String getAbsolutePath() 该方法的作用是获得当前文件或文件夹的绝对路径。例如c:\test\1.t则返回c:\test\1.t。 e、getName方法 public String getName() 该方法的作用是获得当前文件或文件夹的名称。例如c:\test\1.t,则返回1.t。 f、getParent方法 public String getParent() 该方法的作用是获得当前路径中的父路径。例如c:\test\1.t则返回c:\test。 g、isDirectory方法 public boolean isDirectory() 该方法的作用是判断当前File对象是否是目录。 h、isFile方法 public boolean isFile() 该方法的作用是判断当前File对象是否是文件。 i、length方法 public long length() 该方法的作用是返回文件存储时占用的字节数。该数值获得的是文件的实际大小,而不是文件在存储时占用的空间数。 j、list方法 public String[] list() 该方法的作用是返回当前文件夹下所有的文件名和文件夹名称。说明,该名称不是绝对路径。 k、listFiles方法 public File[] listFiles() 该方法的作用是返回当前文件夹下所有的文件对象。 l、mkdir方法 public boolean mkdir() 该方法的作用是创建当前文件文件夹,而不创建该路径中的其它文件夹。假设d盘下只有一个test文件夹,则创建d:\test\abc文件夹则成功,如果创建d:\a\b文件夹则创建失败,因为该路径中d:\a文件夹不存在。如果创建成功则返回true,否则返回false。 m、mkdirs方法 public boolean mkdirs() 该方法的作用是创建文件夹,如果当前路径中包含的父目录不存在时,也会自动根据需要创建。 n、renameTo方法 public boolean renameTo(File dest) 该方法的作用是修改文件名。在修改文件名时不能改变文件路径,如果该路径下已有该文件,则会修改失败。 o、setReadOnly方法 public boolean setReadOnly() 该方法的作用是设置当前文件或文件夹为只读。 3、File类基本示例 以上各方法实现的测试代码如下: import java.io.File; /** * File类使用示例 */ public class FileDemo { public static void main(String[] args) { //创建File对象 File f1 = new File("d:\\test"); File f2 = new File("1.txt"); File f3 = new File("e:\\file.txt"); File f4 = new File("d:\\","1.txt"); //创建文件 try{ boolean b = f3.createNewFile(); }catch(Exception e){ e.printStackTrace(); } //判断文件是否存在 System.out.println(f4.exists()); //获得文件的绝对路径 System.out.println(f3.getAbsolutePath()); //获得文件名 System.out.println(f3.getName()); //获得父路径 System.out.println(f3.getParent()); //判断是否是目录 System.out.println(f1.isDirectory()); //判断是否是文件 System.out.println(f3.isFile()); //获得文件长度 System.out.println(f3.length()); //获得当前文件夹下所有文件和文件夹名称 String[] s = f1.list(); for(int i = 0;i < s.length;i++){ System.out.println(s[i]); } //获得文件对象 File[] f5 = f1.listFiles(); for(int i = 0;i < f5.length;i++){ System.out.println(f5[i]); } //创建文件夹 File f6 = new File("e:\\test\\abc"); boolean b1 = f6.mkdir(); System.out.println(b1); b1 = f6.mkdirs(); System.out.println(b1); //修改文件名 File f7 = new File("e:\\a.txt"); boolean b2 = f3.renameTo(f7); System.out.println(b2); //设置文件为只读 f7.setReadOnly(); } } 4、File类综合示例 下面以两个示例演示File类的综合使用。第一个示例是显示某个文件夹下的所有文件和文件夹,原理是输出当前名称,然后判断当前File对 象是文件还是文件夹,如果则获得该文件夹下的所有子文件和子文件夹,并递归调用该方法实现。第二个示例是删除某个文件夹下的所有文件和文件夹,原理是判断 是否是文件,如果是文件则直接删除,如果是文件夹,则获得该文件夹下所有的子文件和子文件夹,然后递归调用该方法处理所有子文件和子文件夹,然后将空文件 夹删除。则测试时谨慎使用第二个方法,以免删除自己有用的数据文件。示例代码如下: import java.io.File; /** *文件综合使用示例 */ public class AdvanceFileDemo { public static void main(String[] args) { File f = new File("e:\\Book"); printAllFile(f); File f1 = new File("e:\\test"); deleteAll(f1); } /** *打印f路径下所有的文件和文件夹 * @param f文件对象 */ public static void printAllFile(File f){ //打印当前文件名 System.out.println(f.getName()); //是否是文件夹 if(f.isDirectory()){ //获得该文件夹下所有子文件和子文件夹 File[] f1 = f.listFiles(); //循环处理每个对象 int len = f1.length; for(int i = 0;i < len;i++){ //递归调用,处理每个文件对象 printAllFile(f1[i]); } } } /** *删除对象f下的所有文件和文件夹 * @param f文件路径 */ public static void deleteAll(File f){ //文件 if(f.isFile()){ f.delete(); }else{ //文件夹 //获得当前文件夹下的所有子文件和子文件夹 File f1[] = f.listFiles(); //循环处理每个对象 int len = f1.length; for(int i = 0;i < len;i++){ //递归调用,处理每个文件对象 deleteAll(f1[i]); } //删除当前文件夹 f.delete(); } } } 关于File类的使用就介绍这么多,其它的方法和使用时需要注意的问题还需要多进行练习和实际使用。 11.3.1.3读取文件 虽然前面介绍了流的概念,但是这个概念对于初学者来说,还是比较抽象的,下面以实际的读取文件为例子,介绍流的概念,以及输入流的基本使用。 按照前面介绍的知识,将文件中的数据读入程序,是将程序外部的数据传入程序中,应该使用输入流——InputStream或Reader。而由于读取的是特定的数据源——文件,则可以使用输入对应的子类FileInputStream或FileReader实现。 在实际书写代码时,需要首先熟悉读取文件在程序中实现的过程。在Java语言的IO编程中,读取文件是分两个步骤:1、将文件中的数据转换为流,2、读取流内部的数据。其中第一个步骤由系统完成,只需要创建对应的流对象即可,对象创建完成以后步骤1就完成了,第二个步骤使用输入流对象中的read方法即可实现了。 使用输入流进行编程时,代码一般分为3个部分:1、创建流对象,2、读取流对象内部的数据,3、关闭流对象。下面以读取文件的代码示例: import java.io.*; /** *使用FileInputStream读取文件 */ public class ReadFile1 { public static void main(String[] args) { //声明流对象 FileInputStream fis = null; try{ //创建流对象 fis = new FileInputStream("e:\\a.txt"); //读取数据,并将读取到的数据存储到数组中 byte[] data = new byte[1024]; //数据存储的数组 int i = 0; //当前下标 //读取流中的第一个字节数据 int n = fis.read(); //依次读取后续的数据 while(n != -1){ //未到达流的末尾 //将有效数据存储到数组中 data[i] = (byte)n; //下标增加 i++; //读取下一个字节的数据 n = fis.read(); } //解析数据 String s = new String(data,0,i); //输出字符串 System.out.println(s); }catch(Exception e){ e.printStackTrace(); }finally{ try{ //关闭流,释放资源 fis.close(); }catch(Exception e){} } } } 在该示例代码中,首先创建一个FileInputStream类型的对象fis: fis = new FileInputStream("e:\\a.txt"); 这样建立了一个连接到数据源e:\a.txt的流,并将该数据源中的数据转换为流对象fis,以后程序读取数据源中的数据,只需要从流对象fis中读取即可。 读取流fis中的数据,需要使用read方法,该方法是从InputStream类中继承过来的方法,该方法的作用是每次读取流中的一个字节,如果需要读取流中的所有数据,需要使用循环读取,当到达流的末尾时,read方法的返回值是-1。 在该示例中,首先读取流中的第一个字节: int n = fis.read(); 并将读取的值赋值给int值n,如果流fis为空,则n的值是-1,否则n中的最后一个字节包含的时流fis中的第一个字节,该字节被读取以后,将被从流fis中删除。 然后循环读取流中的其它数据,如果读取到的数据不是-1,则将已经读取到的数据n强制转换为byte,即取n中的有效数据——最后一个字节,并存储到数组data中,然后调用流对象fis中的read方法继续读取流中的下一个字节的数据。一直这样循环下去,直到读取到的数据是-1,也就是读取到流的末尾则循环结束。 这里的数组长度是1024,所以要求流中的数据长度不能超过1024,所以该示例代码在这里具有一定的局限性。如果流的数据个数比较多,则可以将1024扩大到合适的个数即可。 经过上面的循环以后,就可以将流中的数据依次存储到data数组中,存储到data数组中有效数据的个数是i个,即循环次数。 其实截至到这里,IO操作中的读取数据已经完成,然后再按照数据源中的数据格式,这里是文件的格式,解析读取出的byte数组即可。 该示例代码中的解析,只是将从流对象中读取到的有效的数据,也就是data数组中的前n个数据,转换为字符串,然后进行输出。 在该示例代码中,只是在catch语句中输出异常的信息,便于代码的调试,在实际的程序中,需要根据情况进行一定的逻辑处理,例如给出提示信息等。 最后在finally语句块中,关闭流对象fis,释放流对象占用的资源,关闭数据源,实现流操作的结束工作。 上面详细介绍了读取文件的过程,其实在实际读取流数据时,还可以使用其它的read方法,下面的示例代码是使用另外一个read方法实现读取的代码: import java.io.FileInputStream; /** *使用FileInputStream读取文件 */ public class ReadFile2 { public static void main(String[] args) { //声明流对象 FileInputStream fis = null; try{ //创建流对象 fis = new FileInputStream("e:\\a.txt"); //读取数据,并将读取到的数据存储到数组中 byte[] data = new byte[1024]; //数据存储的数组 int i = fis.read(data); //解析数据 String s = new String(data,0,i); //输出字符串 System.out.println(s); }catch(Exception e){ e.printStackTrace(); }finally{ try{ //关闭流,释放资源 fis.close(); }catch(Exception e){} } } } 该示例代码中,只使用一行代码: int i = fis.read(data); 就实现了将流对象fis中的数据读取到字节数组data中。该行代码的作用是将 fis流中的数据读取出来,并依次存储到数组data中,返回值为实际读取的有效数据的个数。 使用该中方式在进行读取时,可以简化读取的代码。 当然,在读取文件时,也可以使用Reader类的子类FileReader进行实现,在编写代码时,只需要将上面示例代码中的byte数组替换成char数组即可。 使用FileReader读取文件时,是按照char为单位进行读取的,所以更适合于文本文件的读取,而对于二进制文件或自定义格式的文件来说,还是使用FileInputStream进行读取,方便对于读取到的数据进行解析和操作。 读取其它数据源的操作和读取文件类似,最大的区别在于建立流对象时选择的类不同,而流对象一旦建立,则基本的读取方法是一样,如果只使用最基本的read方法进行读取,则使用基本上是一致的。这也是IO类设计的初衷,使得对于流对象的操作保持一致,简化IO类使用的难度。 程。 基本的输出流包含OutputStream和Writer两个,区别是OutputStream体系中的类(也就是OutputStream的子类)是按照字节写入的,而Writer体系中的类(也就是Writer的子类)是按照字符写入的。 使用输出流进行编程的步骤是: 1、建立输出流 建立对应的输出流对象,也就是完成由流对象到外部数据源之间的转换。 2、向流中写入数据 将需要输出的数据,调用对应的write方法写入到流对象中。 3、关闭输出流 在写入完毕以后,调用流对象的close方法关闭输出流,释放资源。 在使用输出流向外部输出数据时,程序员只需要将数据写入流对象即可,底层的API实现将流对象中的内容写入外部数据源,这个写入的过程对于程序员来说是透明的,不需要专门书写代码实现。 在向文件中输出数据,也就是写文件时,使用对应的文件输出流,包括FileOutputStream和FileWriter两个类,下面以FileOutputStream为例子说明输出流的使用。示例代码如下: import java.io.*; /** *使用FileOutputStream写文件示例 */ public class WriteFile1 { public static void main(String[] args) { String s = "Java语言"; int n = 100; //声明流对象 FileOutputStream fos = null; try{ //创建流对象 fos = new FileOutputStream("e:\\out.txt"); //转换为byte数组 byte[] b1 = s.getBytes(); //换行符 byte[] b2 = "\r\n".getBytes(); byte[] b3 = String.valueOf(n).getBytes(); //依次写入文件 fos.write(b1); fos.write(b2); fos.write(b3); } catch (Exception e) { e.printStackTrace(); }finally{ try{ fos.close(); }catch(Exception e){} } } } 该示例代码写入的文件使用记事本打开以后,内容为: Java语言 100 在该示例代码中,演示了将一个字符串和一个int类型的值依次写入到同一个文件中。在写入文件时,首先创建了一个文件输出流对象fos: fos = new FileOutputStream("e:\\out.txt"); 该对象创建以后,就实现了从流到外部数据源e:\out.txt的连接。说明:当外部文件不存在时,系统会自动创建该文件,但是如果文件路径中包含未创建的目录时将出现异常。这里书写的文件路径可以是绝对路径也可以是相对路径。 在 实际写入文件时,有两种写入文件的方式:覆盖和追加。其中“覆盖”是指清除原文件的内容,写入新的内容,默认采用该种形式写文件,“追加”是指在已有文件 的末尾写入内容,保留原来的文件内容,例如写日志文件时,一般采用追加。在实际使用时可以根据需要采用适合的形式,可以使用: public FileOutputStream(String name, boolean append) throws FileNotFoundException 只需要使用该构造方法在构造FileOutputStream对象时,将第二个参数append的值设置为true即可。 流对象创建完成以后,就可以使用OutputStream中提供的wirte方法向流中依次写入数据了。最基本的写入方法只支持byte数组格式的数据,所以如果需要将内容写入文件,则需要把对应的内容首先转换为byte数组。 这里以如下格式写入数据:首先写入字符串s,使用String类的getBytes方法将该字符串转换为byte数组,然后写入字符串“\r\n”,转换方式同上,该字符串的作用是实现文本文件的换行显示,最后写入int数据n,首先将n转换为字符串,再转换为byte数组。这种写入数据的顺序以及转换为byte数组的方式就是流的数据格式,也就是该文件的格式。因为这里写的都是文本文件,所以写入的内容以明文的形式显示出来,也可以根据自己需要存储的数据设定特定的文件格式。 其实,所有的数据文件,包括图片文件、声音文件等等,都是以一定的数据格式存储数据的,在保存该文件时,将需要保存的数据按照该文件的数据格式依次写入即可,而在打开该文件时,将读取到的数据按照该文件的格式解析成对应的逻辑即可。 最后,在数据写入到流内部以后,如果需要立即将写入流内部的数据强制输出到外部的数据源,则可以使用流对象的flush方法实现。如果不需要强制输出,则只需要在写入结束以后,关闭流对象即可。在关闭流对象时,系统首先将流中未输出到数据源中的数据强制输出,然后再释放该流对象占用的内存空间。 使用FileWriter写入文件时,步骤和创建流对象的操作都和该示例代码一致,只是在转换数据时,需要将写入的数据转换为char数组,对于字符串来说,可以使用String中的toCharArray方法实现转换,然后按照文件格式写入数据即可。 对于其它类型的字节输出流/字符输出流来说,只是在逻辑上连接不同的数据源,在创建对象的代码上会存在一定的不同,但是一旦流对象创建完成以后,基本的写入方法都是write方法,也需要首先将需要写入的数据按照一定的格式转换为对应的byte数组/char数组,然后依次写入即可。 所以IO类的这种设计形式,只需要熟悉该体系中的某一个类的使用以后,就可以触类旁通的学会其它相同类型的类的使用,从而简化程序员的学习,使得使用时保持统一。 10.java的集合类【转】 Posted on 2009-12-03 17:03 火之光 阅读(596) 评论(0) 编辑 收藏 在JDK API中专门设计了一组类,这组类的功能就是实现各种各样方式的数据存储,这样一组专门用来存储其它对象的类,一般被称为对象容器类,简称容器类,这组类和接口的设计结构也被统称为集合框架(Collection Framework)。 这组类和接口都包含在java.util包中。 为了使整个集合框架中的类便于使用,在设计集合框架时大量的使用接口,实际实现的功能类实现对应的接口,这样可以保证各个集合类的使用方式保持统一。 在集合框架中,提供的存储方式共有两种: 1、按照索引值操作数据 在这种存储方式中,为每个存储的数据设定一个索引值,存储在容器中的第一个元素索引值是0,第二个索引值是1,依次类推。在操作数据时按照索引值操作对应的数据,实现这种方式的集合类都实现java.util.Collection接口。 2、按照名称操作数据 在这种存储方式中,为每个存储的数据设定一个名称(任意非null的对象都可以作为名称),以后按照该名称操作该数据,要求名称不能重复,每个名称对应唯一的一个值。这种存储数据的方式也称作名称-数值对,也就是名值对存储。实现这种方式的几个类都实现java.util.Map接口。 这里“按照索引值操作数据”的存储方式,又按照容器内部是否能够存储重复的元素,划分成两类: 1、允许存储重复元素。 这种存储方式中,所有的类都实现了java.util.List接口。 2、不允许存储重复元素。 这种存储方式中,所有的类都实现了java.util.Set接口。 这样,集合框架中的类就分成了三大类: 1、List系列 该系列中的类按照索引值来操作数据,允许存放重复的元素。 2、Set系列 该系列中的类按照索引值来操作数据,不允许存放重复的元素。 3、Map系列 该系列中的类按照名称来操作数据,名称不允许重复,值可以重复,一个名称对应一个唯一的值。 而 在数据结构中,实现数据的存储又可以使用不同的数据结构类型进行存储,例如数组、链表、栈、队列和树等,则以上三类集合框架可以使用不同的数据结构类进行 实现,使用每种数据结构则具备该中数据结构的特点。例如使用数组则访问速度快,使用链表则便于动态插入和删除等,这样就造成了集合框架的复杂性。 另外,在将对象存储到集合类中,为了加快存储的速度,要求被存储对象的类中必须覆盖equals方法和hashCode方法。 对于这些集合类,下面按照以上三个系列的顺序一一进行说明。 9.6.3.1 List系列 List系列的类均实现List接口,大部分的类都以List作为类名的后缀,也有部分该体系中的类命名比较特殊。 该系列中的类,比较常见的有ArrayList和LinkedList两个。其中ArrayList是以数组为基础实现的List,而LinkedList则是以链表为基础实现的List,ArrayList拥有数组的优点,而LinkedList拥有链表的优点。 由于该体系中的类均实现List接口,所以在这些类的内部,相同的功能方法声明是保持一致的,下面进行一一介绍: a、add方法 boolean add(Object o) 该方法的作用是追加对象o到已有容器的末尾。 另外一个add方法: void add(int index, Object element) 该方法的作用是将对象element插入到容器中索引值为index的位置,原来位于该位置的对象以及后续的内容将依次向后移动。 b、addAll方法 boolean addAll(Collection c) 该方法的作用是将容器对象c中的每个元素依次添加到当前容器的末尾。 另外一个addAll方法: boolean addAll(int index, Collection c) 该方法的作用是将容器对象c中的第一个元素插入到当前容器中索引值为index的位置,第二个元素插入到当前容器中索引值为index+1的位置,依次类推。而当前容器中原来位于index以及index索引值以后的元素则依次向后移动。 c、get方法 Object get(int index) 该方法的作用是返回当前容器对象中索引值为index的元素的内容。 d、indexOf方法 int indexOf(Object o) 该方法的作用是查找当前容器中是否存在对象o,如果存在则返回该对象第一次出现位置的索引值,如果不存在则返回-1。 另外一个方法lastIndexOf则是从末尾向前查找,返回从末尾向前第一次出现位置的索引值,如果不存在则返回-1。 e、remove方法 Object remove(int index) 该方法的作用是删除索引值为index的对象的内容,如果删除成功则返回被删除对象的内容。 另外一个remove方法: boolean remove(Object o) 该方法的作用是删除对象内容为o的元素,如果相同的对象有多个,则只删除索引值小的对象。如果删除成功则返回true,否则返回false。 无论使用哪一个remove方法,类内部都自动移动将被删除位置后续的所有元素向前移动,保证索引值的连续性。 f、set方法 Object set(int index, Object element) 该方法的作用是修改索引值为index的内容,将原来的内容修改成对象element的内容。 g、size方法 int size() 该方法的作用是返回当前容器中已经存储的有效元素的个数。 h、toArray方法 Object[] toArray() 该方法的作用是将当前容器中的元素按照顺序转换成一个Object数组。 下面是一个简单的以ArrayList类为基础实现的List系列中类基本使用的示例,代码如下: import java.util.*; /** *以ArrayList类为基础演示List系列类的基本使用 */ public class ArrayListUse { public static void main(String[] args) { //容器对象的初始化 List list = new ArrayList(); //添加数据 list.add("1"); list.add("2"); list.add("3"); list.add("1"); list.add("1"); //插入数据 list.add(1,"12"); //修改数据 list.set(2, "a"); //删除数据 list.remove("1"); //遍历 int size = list.size(); //获得有效个数 //循环有效索引值 for(int i = 0;i < size;i++){ System.out.println((String)list.get(i)); } } } 该程序的运行结果为: 12 a 3 1 1 在List系列中,还包含了Stack(栈)类和Vector(向量)类,Stack类除了实现List系列的功能以外,还实现了栈的结构,主要实现了出栈的pop方法和入栈的push方法。 而Vector类由于需要兼容老版本JDK中缘故,所以在实现的方法中需要提供老版本Vector类中对应的方法,这样导致Vector类中相同或类似的功能方法一般是成对出现的。 Set系列 Set系列中的类都实现了Set接口,该系列中的类均以Set作为类名的后缀。该系列中的容器类,不允许存储重复的元素。也就是当容器中已经存储一个相同的元素时,无法实现添加一个完全相同的元素,也无法将已有的元素修改成和其它元素相同。 Set系列中类的这些特点,使得在某些特殊场合的使用比较适合。 该系列中常见的类有: 1、CopyOnWriteArraySet 以数组为基础实现的Set类。 2、HashSet 以哈希表为基础实现的Set类。 3、LinkedHashSet 以链表为基础实现的Set类。 4、TreeSet 以树为基础实现的Set类。 以不同的数据结构类型实现的Set类,拥有不同数据结构带来的特性,在实际使用时,根据逻辑的需要选择合适的Set类进行使用。 Set系列中的类的方法和List系列中的类的方法要比List系列中少很多,例如不支持插入和修改,而且对于Set系列中元素的遍历也需要转换为专门的Iterator(迭代器)对象才可以进行遍历,遍历时顺序和Set中存储的顺序会有所不同。 下面是以HashSet类为基础实现的示例代码,代码如下: import java.util.*; /** *以HashSet为基础演示Set系列类的基本使用 */ public class HashSetUse { public static void main(String[] args) { //容器对象的初始化 Set set = new HashSet(); //添加元素 set.add("1"); set.add("2"); set.add("3"); set.add("1"); set.add("1"); //删除数据 //set.remove("1"); //遍历 Iterator iterator = set.iterator(); while(iterator.hasNext()){ System.out.println((String)iterator.next()); } } } 该程序的运行结果为: 3 2 1 Map系列 Map系列中的类都实现了Map接口,该系列中的部分类以Map作为类名的后缀。该系列容器类存储元素的方式和以上两种完全不同。 Map提供了一种使用“名称:值”这样的名称和数值对存储数据的方法,在该存储方式中,名称不可以重复,而不同的名称中可以存储相同的数值。具体这种存储的格式将在示例代码中进行实现。 在这种存储结构中,任何不为null的对象都可以作为一个名称(key)来作为存储的值(value)的标识,使用这种形式更利于存储比较零散的数据,也方便数据的查找和获得。Map类中存储的数据没有索引值,系统会以一定的形式索引存储的名称,从而提高读取数据时的速度。 该系列中常见的类有: 1、HashMap 以Hash(哈希表)为基础实现的Map类。 2、LinkedHashMap 以链表和Hash(哈希表)为基础实现的Map类。 3、TreeMap 以树为基础实现的Map类。 和上面的结构类似,以不同的数据结构实现的Map类,拥有不同数据结构的特点,在实际的项目中使用时,根据需要选择合适的即可。 该系列的类中常见的方法如下: a、get方法 Object get(Object key) 该方法的作用是获得当前容器中名称为key的结构对应的值。 b、keySet方法 Set keySet() 该方法的作用是返回当前容器中所有的名称,将所有的名称以Set的形式返回。使用这个方法可以实现对于Map中所有元素的遍历。 c、put方法 Object put(Object key, Object value) 该方法的作用是将值value以名称key的形式存储到容器中。 d、putAll方法 void putAll(Map t) 该方法的作用是将Map对象t中的所有数据按照原来的格式存储到当前容器类中,相当于合并两个Map容器对象。 e、remove方法 Object remove(Object key) 该方法的作用是删除容器中名称为key的值。 f、size方法 int size() 该方法的作用是返回当前日期中存储的名称:值数据的组数。 g、values方法 Collection values() 该方法的作用是返回当前容器所有的值组成的集合,以Collection对象的形式返回。 下面是一个简单的示例,在该示例中演示Map系列类的基本使用,代码如下: import java.util.*; /** *以HashMap为基础演示Map系列中类的使用 */ public class HashMapUse { public static void main(String[] args) { //容器对象的初始化 Map map = new HashMap(); //存储数据 map.put("苹果", "2.5"); map.put("桔子", "2.5"); map.put("香蕉", "3"); map.put("菠萝", "2"); //删除元素 map.remove("桔子"); //修改元素的值 map.put("菠萝", "5"); //获得元素个数 int size = map.size(); System.out.println("个数是:" + size); //遍历Map Set set = map.keySet(); Iterator iterator = set.iterator(); while(iterator.hasNext()){ //获得名称 String name = (String)iterator.next(); //获得数值 String value = (String)map.get(name); //显示到控制台 System.out.println(name + ":" + value); } } } 该程序的运行结果为: 个数是:3 香蕉:3 菠萝:5 苹果:2.5 使用示例 如前所述,集合框架中的类只是提供了一种数据存储的方式,在实际使用时,可以根据逻辑的需要选择合适的集合类进行使用。 下面以一个字符串计算的示例演示集合类的实际使用。 该程序的功能为计算一个数字字符串,例如”1+2*31-5”、”12*30/34-450”等,的计算结果,在该示例中支持四则运算,但是不支持括号。本示例中计算的字符串要求合法。 该程序实现的原理是:首先按照运算符作为间隔,将字符串差分为数字字符串和运算符字符串的序列,由于分拆出的字符串数量不固定,所以存储到List系列的Vector容器中,然后按照运算符的优先级进行计算。 该程序的代码如下: import java.util.*; /** *计算字符串的值 */ public class CalcStr { public static void main(String[] args) { String s = "1+20*3/5"; double d = calc(s); System.out.println(d); } /** *计算字符串的值 * @param s需要计算的字符串 * @return计算结果 */ public static double calc(String s){ //拆分字符串 Vector v = split(s); //print(v); //测试代码 //计算字符串 double d = calcVector(v); return d; } /** *将字符串拆分为数字和运算符。 *例如:"1+23*4"则拆分为:"1"、"+"、"23"、"*"和"4" * @param s需要拆分的字符串 * @return拆分以后的结果 */ private static Vector split(String s){ Vector v = new Vector(); String content = ""; int len = s.length(); //字符串长度 char c; for(int i = 0;i < len;i++){ c = s.charAt(i); //判断是否为运算符 if(c == '+' || c == '-' || c == '*' || c == '/'){ //存储数字 v.add(content); //存储运算符 v.add("" + c); //清除已有字符串 content = ""; }else{ content += c; //连接字符串 } } v.add(content); //添加最后一个数字 return v; } /** *测试代码,输出拆分以后的结果 * @param v需要打印的Vector对象 */ private static void print(Vector v){ int size = v.size(); for(int i = 0;i < size;i++){ System.out.println((String)v.get(i)); } } /** *计算Vector中的数据 * @param v存储拆分后字符串的Vector * @return计算结果 */ private static double calcVector(Vector v){ int index1; int index2; //计算乘除 while(true){ index1 = v.indexOf("*"); //乘号索引值 index2 = v.indexOf("/"); //除号索引值 //无乘除符号 if(index1 == - 1 && index2 == -1){ break; //结束循环 } //如果有乘号 if(index1 != -1){ //没有除号或乘号在前 if(index2 == -1 || index1 < index2){ String s1 = (String)v.get(index1 - 1); //第一个数字 String opr = (String)v.get(index1); //运算符 String s2 = (String)v.get(index1 + 1); //第二个数字 //计算 String answer = calc(s1,s2,opr); //计算以后的处理 handle(answer,index1 - 1,v); } } //有除号 if(index2 != -1){ //没有乘号或除号在前 if(index1 == -1 || index2 < index1){ String s1 = (String)v.get(index2 - 1); //第一个数字 String opr = (String)v.get(index2); //运算符 String s2 = (String)v.get(index2 + 1); //第二个数字 //计算 String answer = calc(s1,s2,opr); //计算以后的处理 handle(answer,index2 - 1,v); } } } //计算加 int index3 = v.indexOf("+"); while(index3 != -1){ //有加号 String s1 = (String)v.get(index3 - 1); //第一个数字 String opr = (String)v.get(index3); //运算符 String s2 = (String)v.get(index3 + 1); // 第二个数字 //计算 String answer = calc(s1,s2,opr); //计算以后的处理 handle(answer,index3 - 1,v); //获得下一个加号的位置 index3 = v.indexOf("+"); } //计算减 index3 = v.indexOf("-"); while(index3 != -1){ //有加号 String s1 = (String)v.get(index3 - 1); //第一个数字 String opr = (String)v.get(index3); //运算符 String s2 = (String)v.get(index3 + 1); //第二个数字 //计算 String answer = calc(s1,s2,opr); //计算以后的处理 handle(answer,index3 - 1,v); //获得下一个减号的位置 index3 = v.indexOf("-"); } //反馈结果 String data = (String)v.get(0); return Double.parseDouble(data); } /** *计算两个字符串类型的值运算结果 * @param number1数字1 * @param number2数字2 * @param opr运算符 * @return运算结果 */ private static String calc(String number1,String number2,String opr){ //将字符串转换为数字 double d1 = Double.parseDouble(number1); double d2 = Double.parseDouble(number2); //判断运算符 if(opr.equals("+")){ return "" + (d1 + d2); } if(opr.equals("-")){ return "" + (d1 - d2); } if(opr.equals("*")){ return "" + (d1 * d2); } if(opr.equals("/")){ return "" + (d1 / d2); } return "0"; //运算符错误时返回0 } /** *计算以后的处理 * @param answer计算结果 * @param index参与计算的三个字符串中第一个字符串的起始位置 * @param v存储字符串的容器 */ private static void handle(String answer,int index,Vector v){ //删除计算过的字符串 for(int i = 0;i < 3;i++){ v.remove(index); } //将计算结果插入到index位置 v.insertElementAt(answer, index); } } 该程序的运行结果为: 13.0 11.JAVA的Random类[转] Posted on 2009-12-03 16:59 火之光 阅读(367) 评论(0) 编辑 收藏 在实际的项目开发过程中,经常需要产生一些随机数值,例如网站登录中的校验数字等,或者需要以一定的几率实现某种效果,例如游戏程序中的物品掉落等。 在Java API中,在java.util包中专门提供了一个和随机处理有关的类,这个类就是Random类。随机数字的生成相关的方法都包含在该类的内部。 Random类中实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。 相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同。这点在生成多个随机数字时需要特别注意。 下面介绍一下Random类的使用,以及如何生成指定区间的随机数组以及实现程序中要求的几率。 1、Random对象的生成 Random类包含两个构造方法,下面依次进行介绍: a、public Random() 该构造方法使用一个和当前系统时间对应的相对时间有关的数字作为种子数,然后使用这个种子数构造Random对象。 b、public Random(long seed) 该构造方法可以通过制定一个种子数进行创建。 示例代码: Random r = new Random(); Random r1 = new Random(10); 再次强调:种子数只是随机算法的起源数字,和生成的随机数字的区间无关。 2、Random类中的常用方法 Random类中的方法比较简单,每个方法的功能也很容易理解。需要说明的是,Random类中各方法生成的随机数字都是均匀分布的,也就是说区间内部的数字生成的几率是均等的。下面对这些方法做一下基本的介绍: a、public boolean nextBoolean() 该方法的作用是生成一个随机的boolean值,生成true和false的值几率相等,也就是都是50%的几率。 b、public double nextDouble() 该方法的作用是生成一个随机的double值,数值介于[0,1.0)之间,这里中括号代表包含区间端点,小括号代表不包含区间端点,也就是0到1之间的随机小数,包含0而不包含1.0。 c、public int nextInt() 该方法的作用是生成一个随机的int值,该值介于int的区间,也就是-231到231-1之间。 如果需要生成指定区间的int值,则需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。 d、public int nextInt(int n) 该方法的作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n。 如果想生成指定区间的int值,也需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。 e、public void setSeed(long seed) 该方法的作用是重新设置Random对象中的种子数。设置完种子数以后的Random对象和相同种子数使用new关键字创建出的Random对象相同。 3、Random类使用示例 使用Random类,一般是生成指定区间的随机数字,下面就一一介绍如何生成对应区间的随机数字。以下生成随机数的代码均使用以下Random对象r进行生成: Random r = new Random(); a、生成[0,1.0)区间的小数 double d1 = r.nextDouble(); 直接使用nextDouble方法获得。 b、生成[0,5.0)区间的小数 double d2 = r.nextDouble() * 5; 因为nextDouble方法生成的数字区间是[0,1.0),将该区间扩大5倍即是要求的区间。 同理,生成[0,d)区间的随机小数,d为任意正的小数,则只需要将nextDouble方法的返回值乘以d即可。 c、生成[1,2.5)区间的小数 double d3 = r.nextDouble() * 1.5 + 1; 生成[1,2.5)区间的随机小数,则只需要首先生成[0,1.5)区间的随机数字,然后将生成的随机数区间加1即可。 同理,生成任意非从0开始的小数区间[d1,d2)范围的随机数字(其中d1不等于0),则只需要首先生成[0,d2-d1)区间的随机数字,然后将生成的随机数字区间加上d1即可。 d、生成任意整数 int n1 = r.nextInt(); 直接使用nextInt方法即可。 e、生成[0,10)区间的整数 int n2 = r.nextInt(10); n2 = Math.abs(r.nextInt() % 10); 以上两行代码均可生成[0,10)区间的整数。 第一种实现使用Random类中的nextInt(int n)方法直接实现。 第二种实现中,首先调用nextInt()方法生成一个任意的int数字,该数字和10取余以后生成的数字区间为(-10,10),因为按照数学上的规定余数的绝对值小于除数,然后再对该区间求绝对值,则得到的区间就是[0,10)了。 同理,生成任意[0,n)区间的随机整数,都可以使用如下代码: int n2 = r.nextInt(n); n2 = Math.abs(r.nextInt() % n); f、生成[0,10]区间的整数 int n3 = r.nextInt(11); n3 = Math.abs(r.nextInt() % 11); 相对于整数区间,[0,10]区间和[0,11)区间等价,所以即生成[0,11)区间的整数。 g、生成[-3,15)区间的整数 int n4 = r.nextInt(18) - 3; n4 = Math.abs(r.nextInt() % 18) - 3; 生成非从0开始区间的随机整数,可以参看上面非从0开始的小数区间实现原理的说明。 h、几率实现 按照一定的几率实现程序逻辑也是随机处理可以解决的一个问题。下面以一个简单的示例演示如何使用随机数字实现几率的逻辑。 在前面的方法介绍中,nextInt(int n)方法中生成的数字是均匀的,也就是说该区间内部的每个数字生成的几率是相同的。那么如果生成一个[0,100)区间的随机整数,则每个数字生成的几率应该是相同的,而且由于该区间中总计有100个整数,所以每个数字的几率都是1%。按照这个理论,可以实现程序中的几率问题。 示例:随机生成一个整数,该整数以55%的几率生成1,以40%的几率生成2,以5%的几率生成3。实现的代码如下: int n5 = r.nextInt(100); int m; //结果数字 if(n5 < 55){ //55个数字的区间,55%的几率 m = 1; }else if(n5 < 95){//[55,95),40个数字的区间,40%的几率 m = 2; }else{ m = 3; } 因为每个数字的几率都是1%,则任意55个数字的区间的几率就是55%,为了代码方便书写,这里使用[0,55)区间的所有整数,后续的原理一样。 当然,这里的代码可以简化,因为几率都是5%的倍数,所以只要以5%为基础来控制几率即可,下面是简化的代码实现: int n6 = r.nextInt(20); int m1; if(n6 < 11){ m1 = 1; }else if(n6 < 19){ m1= 2; }else{ m1 = 3; } 在程序内部,几率的逻辑就可以按照上面的说明进行实现。 4、其它问题 a、相同种子数Random对象问题 前面介绍过,相同种子数的Random对象,相同次数生成的随机数字是完全相同的,下面是测试的代码: Random r1 = new Random(10); Random r2 = new Random(10); for(int i = 0;i < 2;i++){ System.out.println(r1.nextInt()); System.out.println(r2.nextInt()); } 在该代码中,对象r1和r2使用的种子数都是10,则这两个对象相同次数生成的随机数是完全相同的。 如果想避免出现随机数字相同的情况,则需要注意,无论项目中需要生成多少个随机数字,都只使用一个Random对象即可。 b、关于Math类中的random方法 其实在Math类中也有一个random方法,该random方法的工作是生成一个[0,1.0)区间的随机小数。 通过阅读Math类的源代码可以发现,Math类中的random方法就是直接调用Random类中的nextDouble方法实现的。 只是random方法的调用比较简单,所以很多程序员都习惯使用Math类的random方法来生成随机数字。 12.JAVA的Date类与Calendar类【转】 Posted on 2009-12-03 16:56 火之光 阅读(2327) 评论(0) 编辑 收藏 Date类 在JDK1.0中,Date类是唯一的一个代表时间的类,但是由于Date类不便于实现国际化,所以从JDK1.1版本开始,推荐使用Calendar类进行时间和日期处理。这里简单介绍一下Date类的使用。 1、使用Date类代表当前系统时间 Date d = new Date(); System.out.println(d); 使用Date类的默认构造方法创建出的对象就代表当前时间,由于Date类覆盖了toString方法,所以可以直接输出Date类型的对象,显示的结果如下: Sun Mar 08 16:35:58 CST 2009 在该格式中,Sun代表Sunday(周日),Mar代表March(三月),08代表8号,CST代表China Standard Time(中国标准时间,也就是北京时间(东八区))。 2、使用Date类代表指定的时间 Date d1 = new Date(2009-1900,3-1,9); System.out.println(d1); 使用带参数的构造方法,可以构造指定日期的Date类对象,Date类中年份的参数应该是实际需要代表的年份减去1900,实际需要代表的月份减去1以后的值。例如上面的示例代码代表就是2009年3月9号。 实际代表具体的年月日时分秒的日期对象,和这个类似。 3、获得Date对象中的信息 Date d2 = new Date(); //年份 int year = d2.getYear() + 1900; //月份 int month = d2.getMonth() + 1; //日期 int date = d2.getDate(); //小时 int hour = d2.getHours(); //分钟 int minute = d2.getMinutes(); //秒 int second = d2.getSeconds(); //星期几 int day = d2.getDay(); System.out.println("年份:" + year); System.out.println("月份:" + month); System.out.println("日期:" + date); System.out.println("小时:" + hour); System.out.println("分钟:" + minute); System.out.println("秒:" + second); System.out.println("星期:" + day); 使用Date类中对应的get方法,可以获得Date类对象中相关的信息,需要注意的是使用getYear获得是Date对象中年份减去1900以后的值,所以需要显示对应的年份则需要在返回值的基础上加上1900,月份类似。在Date类中还提供了getDay方法,用于获得Date对象代表的时间是星期几,Date类规定周日是0,周一是1,周二是2,后续的依次类推。 4、Date对象和相对时间之间的互转 Date d3 = new Date(2009-1900,3-1,10); long time = 1290876532190L; //将Date类的对象转换为相对时间 long t = d3.getTime(); System.out.println(t); //将相对时间转换为Date类的对象 Date d4 = new Date(time); System.out.println(d4); 使用Date对象中的getTime方法,可以将Date类的对象转换为相对时间,使用Date类的构造方法,可以将相对时间转换为Date类的对象。经过转换以后,既方便了时间的计算,也使时间显示比较直观了。 Calendar类 从JDK1.1版本开始,在处理日期和时间时,系统推荐使用Calendar类进行实现。在设计上,Calendar类的功能要比Date类强大很多,而且在实现方式上也比Date类要复杂一些,下面就介绍一下Calendar类的使用。 Calendar类是一个抽象类,在实际使用时实现特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。 1、使用Calendar类代表当前时间 Calendar c = Calendar.getInstance(); 由于Calendar类是抽象类,且Calendar类的构造方法是protected的,所以无法使用Calendar类的构造方法来创建对象,API中提供了getInstance方法用来创建对象。 使用该方法获得的Calendar对象就代表当前的系统时间,由于Calendar类toString实现的没有Date类那么直观,所以直接输出Calendar类的对象意义不大。 2、使用Calendar类代表指定的时间 Calendar c1 = Calendar.getInstance(); c1.set(2009, 3 - 1, 9); 使用Calendar类代表特定的时间,需要首先创建一个Calendar的对象,然后再设定该对象中的年月日参数来完成。 set方法的声明为: public final void set(int year,int month,int date) 以上示例代码设置的时间为2009年3月9日,其参数的结构和Date类不一样。Calendar类中年份的数值直接书写,月份的值为实际的月份值减1,日期的值就是实际的日期值。 如果只设定某个字段,例如日期的值,则可以使用如下set方法: public void set(int field,int value) 在该方法中,参数field代表要设置的字段的类型,常见类型如下: Calendar.YEAR——年份 Calendar.MONTH——月份 Calendar.DATE——日期 Calendar.DAY_OF_MONTH——日期,和上面的字段完全相同 Calendar.HOUR——12小时制的小时数 Calendar.HOUR_OF_DAY——24小时制的小时数 Calendar.MINUTE——分钟 Calendar.SECOND——秒 Calendar.DAY_OF_WEEK——星期几 后续的参数value代表,设置成的值。例如: c1.set(Calendar.DATE,10); 该代码的作用是将c1对象代表的时间中日期设置为10号,其它所有的数值会被重新计算,例如星期几以及对应的相对时间数值等。 3、获得Calendar类中的信息 Calendar c2 = Calendar.getInstance(); //年份 int year = c2.get(Calendar.YEAR); //月份 int month = c2.get(Calendar.MONTH) + 1; //日期 int date = c2.get(Calendar.DATE); //小时 int hour = c2.get(Calendar.HOUR_OF_DAY); //分钟 int minute = c2.get(Calendar.MINUTE); //秒 int second = c2.get(Calendar.SECOND); //星期几 int day = c2.get(Calendar.DAY_OF_WEEK); System.out.println("年份:" + year); System.out.println("月份:" + month); System.out.println("日期:" + date); System.out.println("小时:" + hour); System.out.println("分钟:" + minute); System.out.println("秒:" + second); System.out.println("星期:" + day); 使用Calendar类中的get方法可以获得Calendar对象中对应的信息,get方法的声明如下: public int get(int field) 其中参数field代表需要获得的字段的值,字段说明和上面的set方法保持一致。需要说明的是,获得的月份为实际的月份值减1,获得的星期的值和Date类不一样。在Calendar类中,周日是1,周一是2,周二是3,依次类推。 4、其它方法说明 其实Calendar类中还提供了很多其它有用的方法,下面简单的介绍几个常见方法的使用。 a、add方法 public abstract void add(int field,int amount) 该方法的作用是在Calendar对象中的某个字段上增加或减少一定的数值,增加是amount的值为正,减少时amount的值为负。 例如在计算一下当前时间100天以后的日期,代码如下: Calendar c3 = Calendar.getInstance(); c3.add(Calendar.DATE, 100); int year1 = c3.get(Calendar.YEAR); //月份 int month1 = c3.get(Calendar.MONTH) + 1; //日期 int date1 = c3.get(Calendar.DATE); System.out.println(year1 + "年" + month1 + "月" + date1 + "日"); 这里add方法是指在c3对象的Calendar.DATE,也就是日期字段上增加100,类内部会重新计算该日期对象中其它各字段的值,从而获得100天以后的日期,例如程序的输出结果可能为: 2009年6月17日 b、after方法 public boolean after(Object when) 该方法的作用是判断当前日期对象是否在when对象的后面,如果在when对象的后面则返回true,否则返回false。例如: Calendar c4 = Calendar.getInstance(); c4.set(2009, 10 - 1, 10); Calendar c5 = Calendar.getInstance(); c5.set(2010, 10 - 1, 10); boolean b = c5.after(c4); System.out.println(b); 在该示例代码中对象c4代表的时间是2009年10月10号,对象c5代表的时间是2010年10月10号,则对象c5代表的日期在c4代表的日期之后,所以after方法的返回值是true。 另外一个类似的方法是before,该方法是判断当前日期对象是否位于另外一个日期对象之前。 c、getTime方法 public final Date getTime() 该方法的作用是将Calendar类型的对象转换为对应的Date类对象,两者代表相同的时间点。 类似的方法是setTime,该方法的作用是将Date对象转换为对应的Calendar对象,该方法的声明如下: public final void setTime(Date date) 转换的示例代码如下: Date d = new Date(); Calendar c6 = Calendar.getInstance(); //Calendar类型的对象转换为Date对象 Date d1 = c6.getTime(); //Date类型的对象转换为Calendar对象 Calendar c7 = Calendar.getInstance(); c7.setTime(d); 5、Calendar对象和相对时间之间的互转 Calendar c8 = Calendar.getInstance(); long t = 1252785271098L; //将Calendar对象转换为相对时间 long t1 = c8.getTimeInMillis(); //将相对时间转换为Calendar对象 Calendar c9 = Calendar.getInstance(); c9.setTimeInMillis(t1); 在转换时,使用Calendar类中的getTimeInMillis方法可以将Calendar 对象转换为相对时间。在将相对时间转换为Calendar对象时,首先创建一个Calendar对象,然后再使用Calendar类的setTimeInMillis方法设置时间即可。 应用示例 下面以两个简单的示例介绍时间和日期处理的基本使用。 1、计算两个日期之间相差的天数 例如计算2010年4月1号和2009年3月11号之间相差的天数,则可以使用时间和日期处理进行计算。 该程序实现的原理为:首先代表两个特定的时间点,这里使用Calendar的对象进行代表,然后将两个时间点转换为对应的相对时间,求两个时间点相对时间的差值,然后除以1天的毫秒数(24小时X60分钟X60秒X1000毫秒)即可获得对应的天数。实现该示例的完整代码如下: import java.util.*; /** *计算两个日期之间相差的天数 */ public class DateExample1 { public static void main(String[] args) { //设置两个日期 //日期:2009年3月11号 Calendar c1 = Calendar.getInstance(); c1.set(2009, 3 - 1, 11); //日期:2010年4月1号 Calendar c2 = Calendar.getInstance(); c2.set(2010, 4 - 1, 1); //转换为相对时间 long t1 = c1.getTimeInMillis(); long t2 = c2.getTimeInMillis(); //计算天数 long days = (t2 - t1)/(24 * 60 * 60 * 1000); System.out.println(days); } } 2、输出当前月的月历 该示例的功能是输出当前系统时间所在月的日历,例如当前系统时间是2009年3月10日,则输出2009年3月的日历。 该程序实现的原理为:首先获得该月1号是星期几,然后获得该月的天数,最后使用流程控制实现按照日历的格式进行输出即可。即如果1号是星期一,则打印一个单位的空格,如果1号是星期二,则打印两个单位的空格,依次类推。打印完星期六的日期以后,进行换行。实现该示例的完整代码如下: import java.util.*; /** *输出当前月的日历 */ public class DateExample2{ public static void main(String[] args){ //获得当前时间 Calendar c = Calendar.getInstance(); //设置代表的日期为1号 c.set(Calendar.DATE,1); //获得1号是星期几 int start = c.get(Calendar.DAY_OF_WEEK); //获得当前月的最大日期数 int maxDay = c.getActualMaximum(Calendar.DATE); //输出标题 System.out.println("星期日 星期一 星期二 星期三 星期四 星期五 星期六"); //输出开始的空格 for(int i = 1;i < start;i++){ System.out.print(" "); } //输出该月中的所有日期 for(int i = 1;i <= maxDay;i++){ //输出日期数字 System.out.print(" " + i); //输出分隔空格 System.out.print(" "); if(i < 10){ System.out.print(' '); } //判断是否换行 if((start + i - 1) % 7 == 0){ System.out.println(); } } //换行 System.out.println(); } } 关于时间和日期的处理就介绍这么多,更多的实现方法还需要根据具体问题进行对应的实现。 13.JAVA的StringBuffer类 Posted on 2009-12-03 16:42 火之光 阅读(7172) 评论(0) 编辑 收藏 StringBuffer类和String一样,也用来代表字符串,只是由于StringBuffer 的内部实现方式和String不同,所以StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。 所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。 在StringBuffer类中存在很多和String类一样的方法,这些方法在功能上和String类中的功能是完全一样的。 但是有一个最显著的区别在于,对于StringBuffer对象的每次修改都会改变对象自身,这点是和String类最大的区别。 另外由于StringBuffer是线程安全的,关于线程的概念后续有专门的章节进行介绍,所以在多线程程序中也可以很方便的进行使用,但是程序的执行效率相对来说就要稍微慢一些。 1、StringBuffer对象的初始化 StringBuffer对象的初始化不像String类的初始化一样,Java提供的有特殊的语法,而通常情况下一般使用构造方法进行初始化。 例如: StringBuffer s = new StringBuffer(); 这样初始化出的StringBuffer对象是一个空的对象。 如果需要创建带有内容的StringBuffer对象,则可以使用: StringBuffer s = new StringBuffer(“abc”); 这样初始化出的StringBuffer对象的内容就是字符串”abc”。 需要注意的是,StringBuffer和String属于不同的类型,也不能直接进行强制类型转换,下面的代码都是错误的: StringBuffer s = “abc”; //赋值类型不匹配 StringBuffer s = (StringBuffer)”abc”; //不存在继承关系,无法进行强转 StringBuffer对象和String对象之间的互转的代码如下: String s = “abc”; StringBuffer sb1 = new StringBuffer(“123”); StringBuffer sb2 = new StringBuffer(s); //String转换为StringBuffer String s1 = sb1.toString(); //StringBuffer转换为String 2、StringBuffer的常用方法 StringBuffer类中的方法主要偏重于对于字符串的变化,例如追加、插入和删除等,这个也是StringBuffer和String类的主要区别。 a、append方法 public StringBuffer append(boolean b) 该方法的作用是追加内容到当前StringBuffer对象的末尾,类似于字符串的连接。调用该方法以后,StringBuffer对象的内容也发生改变,例如: StringBuffer sb = new StringBuffer(“abc”); sb.append(true); 则对象sb的值将变成”abctrue”。 使用该方法进行字符串的连接,将比String更加节约内容,例如应用于数据库 SQL语句的连接,例如: StringBuffer sb = new StringBuffer(); String user = “test”; String pwd = “123”; sb.append(“select * from userInfo where username=“) .append(user) .append(“ and pwd=”) .append(pwd); 这样对象sb的值就是字符串“select * from userInfo where username=test and pwd=123”。 b、deleteCharAt方法 public StringBuffer deleteCharAt(int index) 该方法的作用是删除指定位置的字符,然后将剩余的内容形成新的字符串。例如: StringBuffer sb = new StringBuffer(“Test”); sb. deleteCharAt(1); 该代码的作用删除字符串对象sb中索引值为1的字符,也就是删除第二个字符,剩余的内容组成一个新的字符串。所以对象sb的值变为”Tst”。 还存在一个功能类似的delete方法: public StringBuffer delete(int start,int end) 该方法的作用是删除指定区间以内的所有字符,包含start,不包含end索引值的区间。例如: StringBuffer sb = new StringBuffer(“TestString”); sb. delete (1,4); 该代码的作用是删除索引值1(包括)到索引值4(不包括)之间的所有字符,剩余的字符形成新的字符串。则对象sb的值是”TString”。 c、insert方法 public StringBuffer insert(int offset, boolean b) 该方法的作用是在StringBuffer对象中插入内容,然后形成新的字符串。例如: StringBuffer sb = new StringBuffer(“TestString”); sb.insert(4,false); 该示例代码的作用是在对象sb的索引值4的位置插入false值,形成新的字符串,则执行以后对象sb的值是”TestfalseString”。 d、reverse方法 public StringBuffer reverse() 该方法的作用是将StringBuffer对象中的内容反转,然后形成新的字符串。例如: StringBuffer sb = new StringBuffer(“abc”); sb.reverse(); 经过反转以后,对象sb中的内容将变为”cba”。 e、setCharAt方法 public void setCharAt(int index, char ch) 该方法的作用是修改对象中索引值为index位置的字符为新的字符ch。例如: StringBuffer sb = new StringBuffer(“abc”); sb.setCharAt(1,’D’); 则对象sb的值将变成”aDc”。 f、trimToSize方法 public void trimToSize() 该方法的作用是将StringBuffer对象的中存储空间缩小到和字符串长度一样的长度,减少空间的浪费。 总之,在实际使用时,String和StringBuffer各有优势和不足,可以根据具体的使用环境,选择对应的类型进行使用。 14.JAVA的String 类【转】 Posted on 2009-12-03 16:41 火之光 阅读(7617) 评论(0) 编辑 收藏 String类 1、String对象的初始化 由于String对象特别常用,所以在对String对象进行初始化时,Java 提供了一种简化的特殊语法,格式如下: String s = “abc”; s = “Java语言”; 其实按照面向对象的标准语法,其格式应该为: String s = new String(“abc”); s = new String(“Java语言”); 只是按照面向对象的标准语法,在内存使用上存在比较大的浪费。例如String s = new String(“abc”);实际上创建了两个String对象,一个是”abc”对象,存储在常量空间中,一个是使用new关键字为对象s申请的空间。 其它的构造方法的参数,可以参看String类的API文档。 2、字符串的常见操作 a、charAt方法 该方法的作用是按照索引值(规定字符串中第一个字符的索引值是0,第二个字符的索引值是1,依次类推),获得字符串中的指定字符。例如: String s = “abc”; char c = s.chatAt(1); 则变量c的值是’b’。 b、compareTo方法 该方法的作用是比较两个字符串的大小,比较的原理是依次比较每个字符的字符编码。首先比较两个字符串的第一个字符,如果第一个字符串的字符编码大于第二个的字符串的字符编码,则返回大于 0的值,如果小于则返回小于0的值,如果相等则比较后续的字符,如果两个字符串中的字符编码完全相同则返回0。 例如: String s = “abc”; String s1 = “abd”; int value = s.compareTo(s1); 则value的值是小于0的值,即-1。 在String类中还存在一个类似的方法compareToIgnoreCase,这个方法是忽略字符的大小写进行比较,比较的规则和compareTo一样。例如: String s = “aBc”; String s1 = “ABC”; int value = s. compareToIgnoreCase (s1); 则value的值是0,即两个字符串相等。 c、concat方法 该方法的作用是进行字符串的连接,将两个字符串连接以后形成一个新的字符串。例如: String s = “abc”; String s1 = “def”; String s2 = s.concat(s1); 则连接以后生成的新字符串s2的值是”abcdef”,而字符串s和s1的值不发生改变。如果需要连接多个字符串,可以使用如下方法: String s = “abc”; String s1 = “def”; String s2 = “1234”; String s3 = s.concat(s1).concat(s2); 则生成的新字符串s3的值为”abcdef1234”。 其实在实际使用时,语法上提供了一种更简单的形式,就是使用“+”进行字符串的连接。例如: String s = “abc” + “1234”; 则字符串s的值是”abc1234”,这样书写更加简单直观。 而且使用“+”进行连接,不仅可以连接字符串,也可以连接其他类型。但是要求进行连接时至少有一个参与连接的内容是字符串类型。而且“+”匹配的顺序是从左向右,如果两边连接的内容都是基本数字类型则按照加法运算,如果参与连接的内容有一个是字符串才按照字符串进行连接。 例如: int a = 10; String s = “123” + a + 5; 则连接以后字符串s的值是“123105”,计算的过程为首先连接字符串”123”和变量a的值,生成字符串”12310”,然后使用该字符串再和数字5进行连接生成最终的结果。 而如下代码: int a = 10; String s = a + 5 + “123”; 则连接以后字符串s的值是”15123”,计算的过程为首先计算a和数字5,由于都是数字型则进行加法运算或者数字值15,然后再使用数字值15和字符串”123”进行连接获得最终的结果。 而下面的连接代码是错误的: int a = 12; String s = a + 5 + ‘s’; 因为参与连接的没有一个字符串,则计算出来的结果是数字值,在赋值时无法将一个数字值赋值给字符串s。 d、endsWith方法 该方法的作用是判断字符串是否以某个字符串结尾,如果以对应的字符串结尾,则返回true。 例如: String s = “student.doc”; boolean b = s.endsWith(“doc”); 则变量b的值是true。 e、equals方法 该方法的作用是判断两个字符串对象的内容是否相同。如果相同则返回true,否则返回false。例如: String s = “abc”; String s1 = new String(“abc”); boolean b = s.equals(s1); 而使用“==”比较的是两个对象在内存中存储的地址是否一样。例如上面的代码中,如果判断: boolean b = (s == s1); 则变量b的值是false,因为s对象对应的地址是”abc”的地址,而s1使用new关键字申请新的内存,所以内存地址和s的”abc”的地址不一样,所以获得的值是false。 在String类中存在一个类似的方法equalsIgnoreCase,该方法的作用是忽略大小写比较两个字符串的内容是否相同。例如: String s = “abc”; String s1 =”ABC”; boolean b = s. equalsIgnoreCase (s1); 则变量b的值是true。 f、getBytes方法 该方法的作用是将字符串转换为对应的byte数组,从而便于数据的存储和传输。例如: String s = “计算机”; byte[] b = s.getBytes(); //使用本机默认的字符串转换为 byte数组 byte[] b = s.getBytes(“gb2312”); //使用gb2312字符集转换为byte数组 在实际转换时,一定要注意字符集的问题,否则中文在转换时将会出现问题。 g、indexOf方法 该方法的作用是查找特定字符或字符串在当前字符串中的起始位置,如果不存在则返回-1。例如: String s = “abcded”; int index = s.indexOf(‘d’); int index1 = s.indexOf(‘h’); 则返回字符d在字符串s中第一次出现的位置,数值为3。由于字符h在字符串s中不存在,则index1的值是-1。 当然,也可以从特定位置以后查找对应的字符,例如: int index = s.indexOf(‘d’,4); 则查找字符串s中从索引值4(包括4)以后的字符中第一个出现的字符d,则index的值是5。 由于indexOf是重载的,也可以查找特定字符串在当前字符串中出现的起始位置,使用方式和查找字符的方式一样。 另外一个类似的方法是lastIndexOf方法,其作用是从字符串的末尾开始向前查找第一次出现的规定的字符或字符串,例如: String s = “abcded”; int index = s. lastIndexOf(‘d’); 则index的值是5。 h、length方法 该方法的作用是返回字符串的长度,也就是返回字符串中字符的个数。中文字符也是一个字符。例如: String s = “abc”; String s1 = “Java语言”; int len = s.length(); int len1 = s1.length(); 则变量len的值是3,变量len1的值是6。 i、replace方法 该方法的作用是替换字符串中所有指定的字符,然后生成一个新的字符串。经过该方法调用以后,原来的字符串不发生改变。例如: String s = “abcat”; String s1 = s.replace(‘a’,’1’); 该代码的作用是将字符串s中所有的字符a替换成字符1,生成的新字符串s1的值是”1bc1t”,而字符串s的内容不发生改变。 如果需要将字符串中某个指定的字符串替换为其它字符串,则可以使用replaceAll方法,例如: String s = “abatbac”; String s1 = s.replaceAll(“ba”,”12”); 该代码的作用是将字符串s中所有的字符串”ab”替换为”12”,生成新的字符串”a12t12c”,而字符串s的内容也不发生改变。 如果只需要替换第一个出现的指定字符串时,可以使用replaceFirst方法,例如: String s = “abatbac”; String s1 = s. replaceFirst (“ba”,”12”); 该代码的作用是只将字符串s中第一次出现的字符串”ab”替换为字符串”12”,则字符串s1的值是”a12tbac”,字符串s的内容也不发生改变。 j、split方法 该方法的作用是以特定的字符串作为间隔,拆分当前字符串的内容,一般拆分以后会获得一个字符串数组。例如: String s = “ab,12,df”; String s1[] = s.split(“,”); 该代码的作用是以字符串”,”作为间隔,拆分字符串s,从而得到拆分以后的字符串数字s1,其内容为:{“ab”,”12”,”df”}。 该方法是解析字符串的基础方法。 如果字符串中在内部存在和间隔字符串相同的内容时将拆除空字符串,尾部的空字符串会被忽略掉。例如: String s = “abbcbtbb”; String s1[] = s.split(“b”); 则拆分出的结果字符串数组s1的内容为:{“a”,””,”c”,”t”}。拆分出的中间的空字符串的数量等于中间间隔字符串的数量减一个。例如: String s = “abbbcbtbbb”; String s1[] = s.split(“b”); 则拆分出的结果是:{“a”,””,””,”c”,”t”}。最后的空字符串不论有多少个,都会被忽略。 如果需要限定拆分以后的字符串数量,则可以使用另外一个split方法,例如: String s = “abcbtb1”; String s1[] = s.split(“b”,2); 该代码的作用是将字符串s最多拆分成包含2个字符串数组。则结果为:{“a”,”cbtb1”}。 如果第二个参数为负数,则拆分出尽可能多的字符串,包括尾部的空字符串也将被保留。 k、startsWith方法 该方法的作用和endsWith方法类似,只是该方法是判断字符串是否以某个字符串作为开始。例如: String s = “TestGame”; boolean b = s.startsWith(“Test”); 则变量b的值是true。 l、substring方法 该方法的作用是取字符串中的“子串”,所谓“子串”即字符串中的一部分。例如“23”是字符串“123”的子串。 字符串“123”的子串一共有6个:”1”、”2”、”3”、”12”、”23”、”123”。而”32”不是字符串”123”的子串。 例如: String s = “Test”; String s1 = s.substring(2); 则该代码的作用是取字符串s中索引值为2(包括)以后的所有字符作为子串,则字符串s1的值是”st”。 如果数字的值和字符串的长度相同,则返回空字符串。例如: String s = “Test”; String s1 = s.substring(4); 则字符串s1的值是””。 如果需要取字符串内部的一部分,则可以使用带2个参数的substring方法,例如: String s = “TestString”; String s1 = s.substring(2,5); 则该代码的作用是取字符串s中从索引值2(包括)开始,到索引值5(不包括)的部分作为子串,则字符串s1的值是”stS”。 下面是一个简单的应用代码,该代码的作用是输出任意一个字符串的所有子串。代码如下: String s = “子串示例”; int len = s.length(); //获得字符串长度 for(int begin = 0;begin < len – 1;begin++){ //起始索引值 for(int end = begin + 1;end <= len;end++){ //结束索引值 System.out.println(s.substring(begin,end)); } } 在该代码中,循环变量begin代表需要获得的子串的起始索引值,其变化的区间从第一个字符的索引值0到倒数第二个字符串的索引值len -2,而end代表需要获得的子串的结束索引值,其变化的区间从起始索引值的后续一个到字符串长度。通过循环的嵌套,可以遍历字符串中的所有子串。 m、toCharArray方法 该方法的作用和getBytes方法类似,即将字符串转换为对应的char数组。例如: String s = “abc”; char[] c = s.toCharArray(); 则字符数组c的值为:{‘a’,’b’,’c’}。 n、toLowerCase方法 该方法的作用是将字符串中所有大写字符都转换为小写。例如: String s = “AbC123”; String s1 = s.toLowerCase(); 则字符串s1的值是”abc123”,而字符串s的值不变。 类似的方法是toUpperCase,该方法的作用是将字符串中的小写字符转换为对应的大写字符。例如: String s = “AbC123”; String s1 = s. toUpperCase (); 则字符串s1的值是”ABC123”,而字符串s的值也不变。 o、trim方法 该方法的作用是去掉字符串开始和结尾的所有空格,然后形成一个新的字符串。该方法不去掉字符串中间的空格。例如: String s = “ abc abc 123 “; String s1 = s.trim(); 则字符串s1的值为:” abc abc 123”。字符串s的值不变。 p、valueOf方法 该方法的作用是将其它类型的数据转换为字符串类型。需要注意的是,基本数据和字符串对象之间不能使用以前的强制类型转换的语法进行转换。 另外,由于该方法是static方法,所以不用创建String类型的对象即可。例如: int n = 10; String s = String.valueOf(n); 则字符串s的值是”10”。虽然对于程序员来说,没有发生什么变化,但是对于程序来说,数据的类型却发生了变化。 介绍一个简单的应用,判断一个自然数是几位数字的逻辑代码如下: int n = 12345; String s = String.valueOf(n); int len = s.length(); 则这里字符串的长度len,就代表该自然数的位数。这种判断比数学判断方法在逻辑上要简单一些。 关于String类的使用就介绍这么多,其它的方法以及这里到的方法的详细声明可以参看对应的API文档。 15.java的InputStream和OutputStream的理解【转】 Posted on 2009-12-03 14:19 火之光 阅读(868) 评论(0) 编辑 收藏 1、在java中stream代表一种数据流(源),javaio的底层数据元,---(想像成水龙头) 2、任何有能力产生数据流(源)的javaio对象就可以看作是一个InputStream对象 既然它能产生出数据,我们就可以将数据取出,java对封装的通用方法就read()方法了--(出水龙头) 3、任何有能力接收数据源(流)的javaio对象我们就可以看作是一个OutputStream对象 同样,它能接收数据,我们就可以调用它的write方法,来让它接收数据--(进水龙头了,呵呵) 4、当然,我们可以在Inputstream和OutputStream数据源的基础上,从实际需要触发, 来重新封装出不同性能机制的输入、输出流了,java.io包中提供了很丰富的输入、输出流对象,如: 基于字节流的stream: DataOutputStream----DataInputStream: FileOutputStream-----FileInputStream: .............等,可以用InputStream和OutputStream从JDK文档查阅 基于字符流的stream(典型的以write和reader来标识的): FileWriter---FileReader: StringWriter---StringReader: .........等,你自己可以用Writer和Reader从JDK文档里头查看说明 stream应该是水龙头里的水资源, InputStream:是一个出水龙头(把水封装在里头)的一个实物对象,该对象的read方法呢,就想成这个 出水龙头这一机制对象的开关钮,你read或openStream(其他对象包容InputStream对象的对象方法)一下呢,就等于打开了出水龙头的按钮,水就出来了,里头封装的水是什么性质的呢,你就用相应的容器来装,如string或byte[]..... OutputStream:你就在InputStream基础上反着想就ok了 ------------------------------------------------------------------------------------------------------------------------- OutputStream (1)输出数据 void write(int b)往流中写一个字节b void write(byte b[])往流中写一个字节数组b void write(byte b[],int off,int len)把字节数组b中从下标off开始,长度为len的字节写入流中 (2) flush()刷空输出流,并输出所有被缓存的字节 由于某些流支持缓存功能,该方法将把缓存中所有内容强制输出到流中。 (3)关闭流 close()流操作完毕后必须关闭。 ------------------------------------------------------------------------------------------------------------------------ InputStream (1)从流中读取数据 int read() 读取一个字节,返回值为所读得字节 int read(byte b[]) 读取多个字节,放置到字节数组b中,通常读取的字节数量为b的长度,返回值为实际独取的 字节的数量。 int read(byte b[] ,int off,int len)读取len个字节,放置到以下标off开始字节数组b中,返回值为实际 读取的字节的数量。 int available() 返回值为流中尚未读取的字节的数量。 long skip(long n);读指针跳过n个字节不读,返回值为实际跳过的字节数量 (2)关闭流 close() 流操作完毕后必须关闭 (3)使用输入流中的标记 void mark(int readlimit)纪录当前指针的所在位置.readlimit表示读指针读出的readlimit个字节后 所标记的指针位置才实效。 void reset() 把读指针重新指向用mark方法所记录的位置 boolean markSupported() 当前的流是否支持读指针的记录功能。 ----------------------------------------------------------------------------------------------------------------------- Java IO通过Stream(流)来实现。关于流,可以理解为是一种“数据的管道”。管道中流动的东西可以是基于字节,也可以是基于字符的等。就好像管道里面可以流动水,也可以流动石油一样。 而对应于流还有一个概念:输入、输出设备。这些设备可以是磁盘文件、键盘(输入设备)、显示器(输出设备)、打印机(输出设备)、网络套接字等等。 下面,我们就来了解“流”。 Java中定义了两种类型的流:字节型,和字符型。 字节流:处理字节的输入和输出。包括读写二进制数据等方面的内容。 字符流:处理字符的输入和输出。他采用的是Unicode编码,可以实现国际化。使用字符流的另外一个好处就是:字符流比字节流更有效率。 字节流: 字节流包含两个顶层抽象类:InputStream和OutputStream。 1:字节流的两个顶层类是抽象类,分别是:InputStream和OutputStream。 2:每个抽象类都有子类来实现具体的功能,处理不同的设备的输入和输 出。 下面简单介绍字节流的几个常用子类::: 字节流类 功能简单介绍 DataInputStream 包含了读取Java标准数据类型的输入流 DataOutputStream 包含了写Java标准数据类型的输出流 ByteArrayInputStream 从字节数组读取的输入流 ByteArrayOutputStream 写入字节数组的输出流 FileInputStream 从文件读入的输入流 FileOutputStream 写入文件的输出流 PrintStream 包含最常见的Print()和Println()的输出流 PushbackInputStream 返回一个字节到输入流,主要用于编译器的实现 PipedInputStream 输出管道 PipedOutputStream 输入管道 SequenceInputStream 将n个输入流联合起来,一个接一个按一定顺序读取 RandomAccessFile 随机访问文件 BufferInputStream 缓冲输入流 BufferOutputStream 缓冲输出流 FilterInputStream 实现了InputStream Interface FilterOutputStream 实现了OutputStream Interface InputStream 抽象类,描述流的输入 OutputStream 抽象类,描述流的输入 抽象类InputStream和OutpurStream定义了实用的方法,其中最主要的是read()和 write()。这两个方法在InputStream和OutputStream中声明为抽象方法,由子流类overwrite实现。 2:每个抽象类都有子类来实现具体的功能,处理不同的设备的输入和输 出。 下面简单介绍字节流的几个常用子类::: 字节流类 功能简单介绍 DataInputStream 包含了读取Java标准数据类型的输入流 DataOutputStream 包含了写Java标准数据类型的输出流 ByteArrayInputStream 从字节数组读取的输入流 ByteArrayOutputStream 写入字节数组的输出流 FileInputStream 从文件读入的输入流 FileOutputStream 写入文件的输出流 PrintStream 包含最常见的Print()和Println()的输出流 PushbackInputStream 返回一个字节到输入流,主要用于编译器的实现 PipedInputStream 输出管道 PipedOutputStream 输入管道 SequenceInputStream 将n个输入流联合起来,一个接一个按一定顺序读取 RandomAccessFile 随机访问文件 BufferInputStream 缓冲输入流 BufferOutputStream 缓冲输出流 FilterInputStream 实现了InputStream Interface FilterOutputStream 实现了OutputStream Interface InputStream 抽象类,描述流的输入 OutputStream 抽象类,描述流的输入 抽象类InputStream和OutpurStream定义了实用的方法,其中最主要的是read()和 write()。这两个方法在InputStream和OutputStream中声明为抽象方法,由子流类overwrite实现。 16.一个既然简单又复杂的spring结合hibernate错误 Posted on 2009-12-23 20:21 火之光 阅读(245) 评论(0) 编辑 收藏 环境:eclipse3.3.0+myeclipse5.5.1GA(Spring2.0+struts1.1+Hibernate3.1) 出现的错误是:java.lang.NoSuchMethodError 具体代码: 15:20:54,093 ERROR ContextLoader:203 - Context initialization failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'SessionFactory' defined in class path resource [applicationContext.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: org.objectweb.asm.ClassVisitor.visit(IILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V Caused by: java.lang.NoSuchMethodError: org.objectweb.asm.ClassVisitor.visit(IILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V at net.sf.cglib.core.ClassEmitter.begin_class(ClassEmitter.java:77) at net.sf.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:173) at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216) at net.sf.cglib.core.KeyFactory$Generator.create(KeyFactory.java:145) at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:117) at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:108) at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:104) at net.sf.cglib.proxy.Enhancer.(Enhancer.java:69) at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.getProxyFactory(CGLIBLazyInitializer.java:107) at org.hibernate.proxy.pojo.cglib.CGLIBProxyFactory.postInstantiate(CGLIBProxyFactory.java:43) at org.hibernate.tuple.entity.PojoEntityTuplizer.buildProxyFactory(PojoEntityTuplizer.java:162) at org.hibernate.tuple.entity.AbstractEntityTuplizer.(AbstractEntityTuplizer.java:135) at org.hibernate.tuple.entity.PojoEntityTuplizer.(PojoEntityTuplizer.java:55) at org.hibernate.tuple.entity.EntityEntityModeToTuplizerMapping.(EntityEntityModeToTuplizerMapping.java:56) at org.hibernate.tuple.entity.EntityMetamodel.(EntityMetamodel.java:295) at org.hibernate.persister.entity.AbstractEntityPersister.(AbstractEntityPersister.java:434) at org.hibernate.persister.entity.SingleTableEntityPersister.(SingleTableEntityPersister.java:109) at org.hibernate.persister.PersisterFactory.createClassPersister(PersisterFactory.java:55) at org.hibernate.impl.SessionFactoryImpl.(SessionFactoryImpl.java:226) at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1294) at org.springframework.orm.hibernate3.LocalSessionFactoryBean.newSessionFactory(LocalSessionFactoryBean.java:805) at org.springframework.orm.hibernate3.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:745) at org.springframework.orm.hibernate3.AbstractSessionFactoryBean.afterPropertiesSet(AbstractSessionFactoryBean.java:134) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1202) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1172) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:428) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:251) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:156) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:160) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:284) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:352) at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:244) at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:187) at org.springframework.web.context.ContextLoaderServlet.init(ContextLoaderServlet.java:82) at javax.servlet.GenericServlet.init(GenericServlet.java:212) at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1139) at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:966) at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:3956) at org.apache.catalina.core.StandardContext.start(StandardContext.java:4230) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:760) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:740) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:544) at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:920) at org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:883) at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:492) at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1138) at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:311) at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:120) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1022) at org.apache.catalina.core.StandardHost.start(StandardHost.java:736) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1014) at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443) at org.apache.catalina.core.StandardService.start(StandardService.java:448) at org.apache.catalina.core.StandardServer.start(StandardServer.java:700) at org.apache.catalina.startup.Catalina.start(Catalina.java:552) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:295) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:433) 15:20:54,109 ERROR [/esreport]:676 - StandardWrapper.Throwable org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'SessionFactory' defined in class path resource [applicationContext.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: org.objectweb.asm.ClassVisitor.visit(IILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V Caused by: java.lang.NoSuchMethodError: org.objectweb.asm.ClassVisitor.visit(IILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V at net.sf.cglib.core.ClassEmitter.begin_class(ClassEmitter.java:77) at net.sf.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:173) at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216) at net.sf.cglib.core.KeyFactory$Generator.create(KeyFactory.java:145) at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:117) at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:108) at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:104) at net.sf.cglib.proxy.Enhancer.(Enhancer.java:69) ................................................................................(省略) 原因:Spring 和 Hibernate 共用的一些 jar 文件发生了版本冲突. 解决办法:删除 WEB-INF/lib/asm-2.2.3.jar 然后重启 Tomcat. 17.

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享文档获得金币 ]
0 人已下载

下载文档

相关文档