[细说Java](3)创建字符串是使用" "还是构造函数?

最后更新于:2022-04-01 10:00:16

在Java中,可以使用两种方式创建字符串: ~~~ String x = "abc"; String y = new String("abc"); ~~~ 对于这两种方式(双引号,构造函数)它们到底有什么区别呢? 1.双引号 vs 构造函数 这个问题可以使用这两个简单代码实例来回答: 实例一 ~~~ String a = "abcd"; String b = "abcd"; System.out.println("a == b : "+(a == b)); // true System.out.println("a.equals(b) : "+(a.equals(b))); // true ~~~ a== b等于true 是因为x和y指向方法区中同一个字符串常量,内存引用是相同的。 当相同的字符串常量被多次创建时,只会保存字符串常量的一份副本,这称为“字符串驻留”。在Java中,所有编译时字符串常量都是驻留的。 实例二 ~~~ String c = new String("abcd"); String d = new String("abcd"); System.out.println("c == d : "+(c == d)); // false System.out.println("c.equals(d) : "+(c.equals(d))); // true ~~~ c== d等于false 是因为c和d指向堆中不同的对象。不同的对象拥有不同的内存引用。 下面图论证了以上的结论。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a9a5251.jpg) 2.运行时字符串驻留 运行时也会发生字符串驻留,即使两个字符串是由构造函数方法创建的。 ~~~ String c = new String("abcd").intern(); String d = new String("abcd").intern(); System.out.println("c == d : "+(c == d)); // true System.out.println("c.equals(d) : "+(c.equals(d))); // true (JDK1.7) ~~~ 如果字面值“abcd”已经是字符串类型,那么使用构造函数方式只会创建一个额外没有用处的对象。 因此,如果你只需要创建一个字符串,你可以使用双引号的方式,如果你需要在堆中创建一个新的对象,你可以选择构造函数的方式。 原文链接:[Create Java String Using ” ” or Constructor?](http://www.programcreek.com/2014/03/create-java-string-by-double-quotes-vs-by-constructor/)
';

[细说Java](2)Java中字符串为什么是不可变的

最后更新于:2022-04-01 10:00:14

在Java中字符串(String)是一个不可改变的类。一个不可改变的类只是一个对象实例不可修改的简单类。当创建一个对象实例时,对象实例的所有信息都被初始化,并且信息不能被修改。对于不可改变的类来说还有很多优势。上一篇文章很好说明了为什么字符串被设计成不可改变的。只有你很好的掌握了内存,同步,数据结构等知识后,你才能很好的回答这个问题。 1.字符串常量池的需求 字符串常量区是方法区(Method Area)中一块特殊的存储区域。当一个字符串被创建,如果该字符串已经存在字符串常量区时,相应的返回存在字符串的引用,而不是去新创建一个新的对象返回它的引用。 下面的代码中只会在堆中创建一个字符串对象。 ~~~ String string1 = "abcd"; String string2 = "abcd"; ~~~ 下面的图能很好的说明: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a98c48c.jpg) 如果字符串是可以改变的,改动一个字符串的引用将会导致另一个引用出现错误值。 2.缓存HashCode(哈希码) 在Java中一个字符串的哈希码是经常被使用的,例如在hashMap中。不可改变性确保了哈希码总是会保持不变,这样我们就不必担心发生任何变化。换一句话说,我们没有必要每次使用字符串时都要计算哈希码(因为不可改变性确保哈希码保持不变),这是一种更有效率的方式。 在String类中,都会有下列代码: ~~~ public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ /** Cache the hash code for the string */ private int hash; // Default to 0 } ~~~ 3.促进其他的对象的使用 为了说明更具体一些,考虑下面的程序: ~~~ HashSet<String> set = new HashSet<String>(); set.add(new String("a")); set.add(new String("b")); set.add(new String("c")); for(String a: set) a.value = "a"; ~~~ 在这个例子中,假设String是可变的,那么其值就可以修改,这有违集(set)的设计(集中不包含重复的元素)。这个例子为简单考虑,在真实String类中没有value属性。 4.安全 String在很多Java类中广泛使用作为参数,例如,网络连接,打开文件等等。如果字符串是可改变的,那么网络连接或者文件都可能会被改变,将导致严重的安全威胁。方法还以为是连接到一台机器上,但实际上却没有。可变字符串可能会导致在反射中出现问题,因为这些参数都是字符串。 ~~~ boolean connect(string s){ if (!isSecure(s)) { throw new SecurityException(); } //如果在这之前s通过其他引用进行改变,这可能会出现问题 causeProblem(s); } ~~~ 5.不可变对象是线程安全的 因为不可变对象是不可改变的,那么它们可以在多个线程之间自由的共享。这就取消了进行同步的需求。 总之,字符串被设计为不可变是为了效率和安全性考虑。这也就是一般情况下优先选择不可变对象的原因。 原文:[Why String is immutable in Java ?](http://www.programcreek.com/2013/04/why-string-is-immutable-in-java/)
';

[细说Java](1)图说字符串的不变性

最后更新于:2022-04-01 10:00:11

我们用下面一组图来说明Java的不变性。 1.声明一个字符串 ~~~ String s = "abcd"; ~~~ s存储了字符串对象的引用。下面图片中的箭头就表示这种存储引用。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a94ace3.jpg) 2.将一个字符串变量赋值给另外一个字符串变量 ~~~ String s2 = s; ~~~ s2变量存储了同样的引用值。所以,两个变量指向同一个字符串对象。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a9634af.jpg) 3.合并字符串 ~~~ s = s.concat("ef"); ~~~ s现在存储的是新生成的字符串对象的引用。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a974a24.jpg) 4.总结 一旦一个字符串在内存(堆)上创建,这个字符串就不会改变。我们应该注意到String类的所有方法都不会改变字符串本身,而是返回一个新的字符串。 如果我们需要一个可以改变的字符串,我们可以使用StringBuffer或者StringBuilder。否则,因为每次都是创建一个新的字符串。 原文链接:[Diagram to show Java String’s Immutability](http://www.programcreek.com/2009/02/diagram-to-show-java-strings-immutability/) 译文链接:[[图说Java]图说字符串的不变性](http://blog.csdn.net/sunnyyoona/article/details/50410801)
';

[Java]Java工程师成神之路

最后更新于:2022-04-01 10:00:09

### 一、基础篇 ### 1.1 JVM #### 1.1.1. Java内存模型,Java内存管理,Java堆和栈,垃圾回收 http://www.jcp.org/en/jsr/detail?id=133 http://ifeve.com/jmm-faq/ #### 1.1.2. 了解JVM各种参数及调优 #### 1.1.3. 学习使用Java工具 jps, jstack, jmap, jconsole, jinfo, jhat, javap, … http://kenai.com/projects/btrace http://www.crashub.org/ https://github.com/taobao/TProfiler https://github.com/CSUG/HouseMD http://wiki.cyclopsgroup.org/jmxterm https://github.com/jlusdy/TBJMap #### 1.1.4. 学习Java诊断工具 http://www.eclipse.org/mat/ http://visualvm.java.net/oqlhelp.html #### 1.1.5. 自己编写各种outofmemory,stackoverflow程序 HeapOutOfMemory Young OutOfMemory MethodArea OutOfMemory ConstantPool OutOfMemory DirectMemory OutOfMemory Stack OutOfMemory Stack OverFlow #### 1.1.6. 使用工具尝试解决以下问题,并写下总结 当一个Java程序响应很慢时如何查找问题 当一个Java程序频繁FullGC时如何解决问题,如何查看垃圾回收日志 当一个Java应用发生OutOfMemory时该如何解决,年轻代、年老代、永久代解决办法不同,导致原因也不同 #### 1.1.7. 参考资料 http://docs.oracle.com/javase/specs/jvms/se7/html/ http://www.cs.umd.edu/~pugh/java/memoryModel/ http://gee.cs.oswego.edu/dl/jmm/cookbook.html ### 1.2. Java基础知识 #### 1.2.1. 阅读源代码 java.lang.String java.lang.Integer java.lang.Long java.lang.Enum java.math.BigDecimal java.lang.ThreadLocal java.lang.ClassLoader & java.net.URLClassLoader java.util.ArrayList & java.util.LinkedList java.util.HashMap & java.util.LinkedHashMap & java.util.TreeMap java.util.HashSet & java.util.LinkedHashSet & java.util.TreeSet #### 1.2.2. 熟悉Java中各种变量类型 #### 1.2.3. 熟悉Java String的使用,熟悉String的各种函数 #### 1.2.4. 熟悉Java中各种关键字 #### 1.2.5. 学会使用List,Map,Stack,Queue,Set 上述数据结构的遍历 上述数据结构的使用场景 Java实现对Array/List排序 java.uti.Arrays.sort() java.util.Collections.sort() Java实现对List去重 Java实现对List去重,并且需要保留数据原始的出现顺序 Java实现最近最少使用cache,用LinkedHashMap #### 1.2.6. Java IO&Java NIO,并学会使用 java.io.* java.nio.* nio和reactor设计模式 文件编码,字符集 #### 1.2.7. Java反射与javassist 反射与工厂模式 java.lang.reflect.* #### 1.2.8. Java序列化 java.io. Serializable 什么是序列化,为什么序列化 序列化与单例模式 google序列化protobuf #### 1.2.9. 虚引用,弱引用,软引用 java.lang.ref.* 实验这些引用的回收 #### 1.2.10. 熟悉Java系统属性 java.util.Properties #### 1.2.11. 熟悉Annotation用法 java.lang.annotation.* #### 1.2.12. JMS javax.jms.* #### 1.2.13. JMX java.lang.management.* javax.management.* #### 1.2.14. 泛型和继承,泛型和擦除 #### 1.2.15. 自动拆箱装箱与字节码 #### 1.2.16. 实现Callback #### 1.2.17. java.lang.Void类使用 #### 1.2.18. Java Agent,premain函数 java.lang.instrument #### 1.2.19. 单元测试 Junit,http://junit.org/ Jmockit,https://code.google.com/p/jmockit/ djUnit,http://works.dgic.co.jp/djunit/ #### 1.2.20. Java实现通过正则表达式提取一段文本中的电子邮件,并将@替换为#输出 java.lang.util.regex.* #### 1.2.21. 学习使用常用的Java工具库 commons.lang, commons.*… guava-libraries netty #### 1.2.22. 什么是API&SPI http://en.wikipedia.org/wiki/Application_programming_interface http://en.wikipedia.org/wiki/Service_provider_interface #### 1.2.23. 参考资料 JDK src.zip 源代码 http://openjdk.java.net/ http://commons.apache.org/ https://code.google.com/p/guava-libraries/ http://netty.io/ http://stackoverflow.com/questions/2954372/difference-between-spi-and-api http://stackoverflow.com/questions/11404230/how-to-implement-the-api-spi-pattern-in-java ### 1.3. Java并发编程 #### 1.3.1. 阅读源代码,并学会使用 java.lang.Thread java.lang.Runnable java.util.concurrent.Callable java.util.concurrent.locks.ReentrantLock java.util.concurrent.locks.ReentrantReadWriteLock java.util.concurrent.atomic.Atomic* java.util.concurrent.Semaphore java.util.concurrent.CountDownLatch java.util.concurrent.CyclicBarrier java.util.concurrent.ConcurrentHashMap java.util.concurrent.Executors #### 1.3.2. 学习使用线程池,自己设计线程池需要注意什么 #### 1.3.3. 锁 什么是锁,锁的种类有哪些,每种锁有什么特点,适用场景是什么 在并发编程中锁的意义是什么 #### 1.3.4. synchronized的作用是什么,synchronized和lock #### 1.3.5. sleep和wait #### 1.3.6. wait和notify #### 1.3.7. 写一个死锁的程序 #### 1.3.8. 什么是守护线程,守护线程和非守护线程的区别以及用法 #### 1.3.9. volatile关键字的理解 C++ volatile关键字和Java volatile关键字 happens-before语义 编译器指令重排和CPU指令重排 http://en.wikipedia.org/wiki/Memory_ordering http://en.wikipedia.org/wiki/Volatile_variable http://preshing.com/20130702/the-happens-before-relation/ #### 1.3.10. 以下代码是不是线程安全?为什么?如果为count加上volatile修饰是否能够做到线程安全?你觉得该怎么做是线程安全的? ~~~ public class Sample { private static int count = 0; public static void increment() { count++; } } ~~~ #### 1.3.11. 解释一下下面两段代码的差别 ~~~ // 代码1 public class Sample { private static int count = 0; synchronized public static void increment() { count++; } } // 代码2 public class Sample { private static AtomicInteger count = new AtomicInteger(0); public static void increment() { count.getAndIncrement(); } } ~~~ #### 1.3.12. 参考资料 http://book.douban.com/subject/10484692/ http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html ### 二、 进阶篇 ### 2.1. Java底层知识 #### 2.1.1. 学习了解字节码、class文件格式 http://en.wikipedia.org/wiki/Java_class_file http://en.wikipedia.org/wiki/Java_bytecode http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/ http://asm.ow2.org/ #### 2.1.2. 写一个程序要求实现javap的功能(手工完成,不借助ASM等工具) 如Java源代码: ~~~ public static void main(String[] args) { int i = 0; i += 1; i *= 1; System.out.println(i); } ~~~ 编译后读取class文件输出以下代码: ~~~ public static void main(java.lang.String[]); Code: Stack=2, Locals=2, Args_size=1 0: iconst_0 1: istore_1 2: iinc 1, 1 5: iload_1 6: iconst_1 7: imul 8: istore_1 9: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 12: iload_1 13: invokevirtual #3; //Method java/io/PrintStream.println:(I)V 16: return LineNumberTable: line 4: 0 line 5: 2 line 6: 5 line 7: 9 line 8: 16 ~~~ #### 2.1.3. CPU缓存,L1,L2,L3和伪共享 http://duartes.org/gustavo/blog/post/intel-cpu-caches/ http://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html #### 2.1.4. 什么是尾递归 #### 2.1.5. 熟悉位运算 用位运算实现加、减、乘、除、取余 #### 2.1.6. 参考资料 http://book.douban.com/subject/1138768/ http://book.douban.com/subject/6522893/ http://en.wikipedia.org/wiki/Java_class_file http://en.wikipedia.org/wiki/Java_bytecode http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings ### 2.2. 设计模式 #### 2.2.1. 实现AOP CGLIB和InvocationHandler的区别 http://cglib.sourceforge.net/ 动态代理模式 Javassist实现AOP http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/ ASM实现AOP http://asm.ow2.org/ #### 2.2.2. 使用模板方法设计模式和策略设计模式实现IOC #### 2.2.3. 不用synchronized和lock,实现线程安全的单例模式 #### 2.2.4. nio和reactor设计模式 #### 2.2.5. 参考资料 http://asm.ow2.org/ http://cglib.sourceforge.net/ http://www.javassist.org/ ### 2.3. 网络编程知识 #### 2.3.1. Java RMI,Socket,HttpClient #### 2.3.2. 用Java写一个简单的静态文件的HTTP服务器 实现客户端缓存功能,支持返回304 实现可并发下载一个文件 使用线程池处理客户端请求 使用nio处理客户端请求 支持简单的rewrite规则 上述功能在实现的时候需要满足“开闭原则” #### 2.3.3. 了解nginx和apache服务器的特性并搭建一个对应的服务器 http://nginx.org/ http://httpd.apache.org/ #### 2.3.4. 用Java实现FTP、SMTP协议 #### 2.3.5. 什么是CDN?如果实现?DNS起到什么作用? 搭建一个DNS服务器 搭建一个 Squid 或 Apache Traffic Server 服务器 http://www.squid-cache.org/ http://trafficserver.apache.org/ http://en.wikipedia.org/wiki/Domain_Name_System #### 2.3.6. 参考资料 http://www.ietf.org/rfc/rfc2616.txt http://tools.ietf.org/rfc/rfc5321.txt http://en.wikipedia.org/wiki/Open/closed_principle ### 2.4. 框架知识 spring,spring mvc,阅读主要源码 ibatis,阅读主要源码 用spring和ibatis搭建java server ### 2.5. 应用服务器知识 熟悉使用jboss,https://www.jboss.org/overview/ 熟悉使用tomcat,http://tomcat.apache.org/ 熟悉使用jetty,http://www.eclipse.org/jetty/ ### 三、 高级篇 ### 3.1. 编译原理知识 #### 3.1.1. 用Java实现以下表达式解析并返回结果(语法和Oracle中的select sysdate-1 from dual类似) ~~~ sysdate sysdate - 1 sysdate - 1/24 sysdate - 1/(12*2) ~~~ #### 3.1.2. 实现对一个List通过DSL筛选 ~~~ QList<Map<String, Object>> mapList = new QList<Map<String, Object>>; mapList.add({"name": "hatter test"}); mapList.add({"id": -1,"name": "hatter test"}); mapList.add({"id": 0, "name": "hatter test"}); mapList.add({"id": 1, "name": "test test"}); mapList.add({"id": 2, "name": "hatter test"}); mapList.add({"id": 3, "name": "test hatter"}); mapList.query("id is not null and id > 0 and name like '%hatter%'"); ~~~ 要求返回列表中匹配的对象,即最后两个对象; #### 3.1.3. 用Java实现以下程序(语法和变量作用域处理都和JavaScript类似): 代码: ~~~ var a = 1; var b = 2; var c = function() { var a = 3; println(a); println(b); }; c(); println(a); println(b); ~~~ 输出: ~~~ 3 2 1 2 ~~~ #### 3.1.4. 参考资料 http://en.wikipedia.org/wiki/Abstract_syntax_tree https://javacc.java.net/ http://www.antlr.org/ ### 3.2. 操作系统知识 Ubuntu Centos 使用linux,熟悉shell脚本 ### 3.3. 数据存储知识 #### 3.3.1. 关系型数据库 MySQL 如何看执行计划 如何搭建MySQL主备 binlog是什么 Derby,H2,PostgreSQL SQLite ### 3.3.2. NoSQL Cache Redis Memcached Leveldb Bigtable HBase Cassandra Mongodb 图数据库 neo4j ### 3.3.3. 参考资料 http://db-engines.com/en/ranking http://redis.io/ https://code.google.com/p/leveldb/ http://hbase.apache.org/ http://cassandra.apache.org/ http://www.mongodb.org/ http://www.neo4j.org/ ### 3.4. 大数据知识 #### 3.4.1. Zookeeper,在linux上部署zk #### 3.4.2. Solr,Lucene,ElasticSearch 在linux上部署solr,solrcloud,,新增、删除、查询索引 #### 3.4.3. Storm,流式计算,了解Spark,S4 在linux上部署storm,用zookeeper做协调,运行storm hello world,local和remote模式运行调试storm topology。 #### 3.4.4. Hadoop,离线计算 Hdfs:部署NameNode,SecondaryNameNode,DataNode,上传文件、打开文件、更改文件、删除文件 MapReduce:部署JobTracker,TaskTracker,编写mr job Hive:部署hive,书写hive sql,得到结果 Presto:类hive,不过比hive快,非常值得学习 #### 3.4.5. 分布式日志收集flume,kafka,logstash #### 3.4.6. 数据挖掘,mahout #### 3.4.7. 参考资料 http://zookeeper.apache.org/ https://lucene.apache.org/solr/ https://github.com/nathanmarz/storm/wiki http://hadoop.apache.org/ http://prestodb.io/ http://flume.apache.org/,http://logstash.net/,http://kafka.apache.org/ http://mahout.apache.org/ ### 3.5. 网络安全知识 #### 3.5.1. 什么是DES、AES #### 3.5.2. 什么是RSA、DSA #### 3.5.3. 什么是MD5,SHA1 #### 3.5.4. 什么是SSL、TLS,为什么HTTPS相对比较安全 #### 3.5.5. 什么是中间人攻击、如果避免中间人攻击 #### 3.5.6. 什么是DOS、DDOS、CC攻击 #### 3.5.7. 什么是CSRF攻击 #### 3.5.8. 什么是CSS攻击 #### 3.5.9. 什么是SQL注入攻击 #### 3.5.10. 什么是Hash碰撞拒绝服务攻击 #### 3.5.11. 了解并学习下面几种增强安全的技术 > http://www.openauthentication.org/ HOTP http://www.ietf.org/rfc/rfc4226.txt TOTP http://tools.ietf.org/rfc/rfc6238.txt OCRA http://tools.ietf.org/rfc/rfc6287.txt http://en.wikipedia.org/wiki/Salt_(cryptography) #### 3.5.12. 用openssl签一个证书部署到apache或nginx #### 3.5.13. 参考资料 http://en.wikipedia.org/wiki/Cryptographic_hash_function http://en.wikipedia.org/wiki/Block_cipher http://en.wikipedia.org/wiki/Public-key_cryptography http://en.wikipedia.org/wiki/Transport_Layer_Security http://www.openssl.org/ https://code.google.com/p/google-authenticator/ ### 四、 扩展篇 ### 4.1. 相关知识 #### 4.1.1. 云计算,分布式,高可用,可扩展 #### 4.1.2. 虚拟化 https://linuxcontainers.org/ http://www.linux-kvm.org/page/Main_Page http://www.xenproject.org/ https://www.docker.io/ #### 4.1.3. 监控 http://www.nagios.org/ http://ganglia.info/ #### 4.1.4. 负载均衡 http://www.linuxvirtualserver.org/ #### 4.1.5. 学习使用git https://github.com/ https://git.oschina.net/ #### 4.1.6. 学习使用maven http://maven.apache.org/ #### 4.1.7. 学习使用gradle http://www.gradle.org/ #### 4.1.8. 学习一个小语种语言 Groovy Scala LISP, Common LISP, Schema, Clojure R Julia Lua Ruby #### 4.1.9. 尝试了解编码的本质 了解以下概念 ASCII, ISO-8859-1 GB2312, GBK, GB18030 Unicode, UTF-8 不使用 String.getBytes() 等其他工具类/函数完成下面功能 ~~~ public static void main(String[] args) throws IOException { String str = "Hello, 我们是中国人。"; byte[] utf8Bytes = toUTF8Bytes(str); FileOutputStream fos = new FileOutputStream("f.txt"); fos.write(utf8Bytes); fos.close(); } public static byte[] toUTF8Bytes(String str) { return null; // TODO } ~~~ 想一下上面的程序能不能写一个转GBK的? 写个程序自动判断一个文件是哪种编码 #### 4.1.10. 尝试了解时间的本质 时区 & 冬令时、夏令时 http://en.wikipedia.org/wiki/Time_zone ftp://ftp.iana.org/tz/data/asia http://zh.wikipedia.org/wiki/%E4%B8%AD%E5%9C%8B%E6%99%82%E5%8D%80 闰年 http://en.wikipedia.org/wiki/Leap_year 闰秒 ftp://ftp.iana.org/tz/data/leapseconds System.currentTimeMillis() 返回的时间是什么 #### 4.1.11. 参考资料 http://git-scm.com/ http://en.wikipedia.org/wiki/UTF-8 http://www.iana.org/time-zones ### 4.2. 扩展学习 #### 4.2.1. JavaScript知识 ##### 4.2.1.1. 什么是prototype 修改代码,使程序输出“1 3 5”: http://jsfiddle.net/Ts7Fk/ ##### 4.2.1.2. 什么是闭包 看一下这段代码,并解释一下为什么按Button1时没有alert出“This is button: 1”,如何修改: http://jsfiddle.net/FDPj3/1/ ##### 4.2.1.3. 了解并学习一个JS框架 jQuery ExtJS ArgularJS ##### 4.2.1.4. 写一个Greasemonkey插件 ~~~ http://en.wikipedia.org/wiki/Greasemonkey ~~~ ##### 4.2.1.5. 学习node.js http://nodejs.org/ #### 4.2.2. 学习html5 ArgularJS,https://docs.angularjs.org/api #### 4.2.3. 参考资料 http://www.ecmascript.org/ http://jsfiddle.net/ http://jsbin.com/ http://runjs.cn/ http://userscripts.org/ ### 五、 推荐书籍 > 《深入Java虚拟机》 《深入理解Java虚拟机》 《Effective Java》 《七周七语言》 《七周七数据》 《Hadoop技术内幕》 《Hbase In Action》 《Mahout In Action》 《这就是搜索引擎》 《Solr In Action》 《深入分析Java Web技术内幕》 《大型网站技术架构》 《高性能MySQL》 《算法导论》 《计算机程序设计艺术》 《代码大全》 《JavaScript权威指南》  
';

[Java开发之路](21)Comparator与Comparable

最后更新于:2022-04-01 10:00:07

### 1\. Comparable ~~~ package java.lang; import java.util.*; public interface Comparable<T> {    public int compareTo(T o); } ~~~ 说明: Comparable 是排序接口。若一个类实现了Comparable接口,则该类可以支持排序。 假设现在存在实现Comparable接口的类的实例的List列表(或数组),则该List列表(或数组)可以通过 Collections.sort(或 Arrays.sort)进行排序。 假设我们通过 x.compareTo(y) 来“比较x和y的大小”。若返回“负数”,意味着“x比y小”;返回“零”,意味着“x等于y”;返回“正数”,意味着“x大于y”。 举例: ~~~ package com.qunar.test; /** * Created by xiaosi on 16-3-7. */ public class Student implements Comparable<Student>{    private String name;    private int age;    public Student(String name, int age) {        this.name = name;        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    @Override    public int compareTo(Student stu) {        if(age == stu.getAge()){            return name.compareTo(stu.getName());        }//if        else if(age > stu.getAge()){            return 1;        }        return -1;    } } ~~~ ~~~        List<Student> stus = new ArrayList<Student>();        stus.add(new Student("xiaosi",24));        stus.add(new Student("sunny",24));        stus.add(new Student("yoona",21));        stus.add(new Student("kim",27));        Collections.sort(stus);        for(Student stu : stus){            System.out.println("age" + stu.getAge() + "   name->" + stu.getName());        } ~~~ 以上实例实现的功能是:按student的age排序,如果年龄相同,则按name排序。 ### 2\. Comparator ~~~ package java.util; public interface Comparator<T> {    int compare(T o1, T o2);    boolean equals(Object obj); } ~~~ 说明: 若一个类本身不支持排序,并且没有实现Comparable接口。那么我们可以建立一个该类的比较器来进行排序。这个比较器只需要实现Comparator接口即可。我们可以通过比较器,然后通过该比较器对类进行排序。 举例: ~~~ package com.qunar.test; /** * Created by xiaosi on 16-3-7. */ public class Teacher {    private String name;    private int age;    public Teacher(String name, int age) {        this.name = name;        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    } } ~~~ 比较器: ~~~ package com.qunar.test; import java.util.Comparator; /** * Created by xiaosi on 16-3-7. * Teacher比较器 */ public class TeacherComparator implements Comparator<Teacher>{    @Override    public int compare(Teacher o1, Teacher o2) {        if(o1.getAge() == o2.getAge()){            return o1.getName().compareTo(o2.getName());        }//if        else if(o1.getAge() > o2.getAge()){            return 1;        }        return -1;    } } ~~~ ~~~        List<Teacher> teachers = new ArrayList<Teacher>();        teachers.add(new Teacher("xiaosi",24));        teachers.add(new Teacher("sunny",24));        teachers.add(new Teacher("yoona",21));        teachers.add(new Teacher("kim",27));        Collections.sort(teachers,new TeacherComparator());        for(Teacher te : teachers){            System.out.println("age" + te.getAge() + "   name->" + te.getName());        } ~~~ ### 3\. 比较 Comparable 是一个对象本身就已经支持自比较所需要实现的接口(如 String、Integer 自己就可以完成比较大小操作)。 Comparator 是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。可以说一个是自己完成比较,一个是外部程序实现比较的差别而已。 用 Comparator 是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。比如:你想对整数采用绝对值大小来排序,Integer 是不符合要求的,你不需要去修改 Integer 类(实际上你也不能这么做)去改变它的排序行为,只要使用一个实现了 Comparator 接口的对象来实现控制它的排序就行了。 Comparable使用:将比较方法写到实体类内对外声明所有比较规则统一按照这一种方式,代码的通用性差,如果改变一种比较规则,代码需要重写 Comparator使用:这种方式的比较实现了实体和比较规则的解绑,实现的比较规则可以根据自己的业务需求调用对应的比较类
';

[Java开发之路](20)try-with-resource 异常声明

最后更新于:2022-04-01 10:00:04

Try-with-resources是java7中一个新的异常处理机制,它能够很容易地关闭在try-catch语句块中使用的资源。 在java7以前,程序中使用的资源需要被明确地关闭,过程有点繁琐,如下所示: ~~~ package com.qunar.lectures.tryResource; import java.io.*; import java.util.ArrayList; import java.util.List; /** * Created by xiaosi on 16-3-4. */ public class TryResourceDemo {    // 获取资源数据    public static List<String> readLines(String resourcePath) {        String path = TryResourceDemo.class.getResource(resourcePath).getPath();        File file = new File(path);        if (!file.exists()) {            throw new RuntimeException("Can not find file + " + resourcePath);        }//if        if (!file.isFile()) {            throw new RuntimeException(resourcePath + " is not a regular file");        }//if        FileInputStream fis;        InputStreamReader isr;        BufferedReader br = null;        try {            fis = new FileInputStream(file);            isr = new InputStreamReader(fis, "UTF-8");            br = new BufferedReader(isr);            List<String> lines = new ArrayList<String>();            String line;            while ((line = br.readLine()) != null) {                lines.add(line);            }//while            return lines;        }        catch (IOException e) {            throw new RuntimeException("Read file failed , file=" + resourcePath, e);        }        finally {            if(br != null){                try {                    br.close();                } catch (IOException e) {                    e.printStackTrace();                }            }//if        }//finally    }    public static void main(String[] args) {        List<String> lines = readLines("/a.txt");        for(String line : lines){            System.out.println("line:" + line);        }//for    } } ~~~ 假设try语句块抛出一个异常,然后finally语句块被执行。同样假设finally语句块也抛出了一个异常。那么哪个异常会根据调用栈往外传播?即使try语句块中抛出的异常与异常传播更相关,最终还是finally语句块中抛出的异常会根据调用栈向外传播。 ~~~ private static void printFileJava7() throws IOException {    try(FileInputStream input = new FileInputStream("file.txt")) {        int data = input.read();        while(data != -1){            System.out.print((char) data);            data = input.read();        }    } } ~~~ 我们看到第一行: ~~~ try(FileInputStream input = new FileInputStream("file.txt")) { ~~~ 这就是try-with-resource 结构的用法。FileInputStream 类型变量就在try关键字后面的括号中声明并赋值。在这声明的变量我们可以在下面的代码中直接使用,即同一个作用域中。当try语句块运行结束时,FileInputStream会被自动关闭。这是因为FileInputStream 实现了java中的java.lang.AutoCloseable接口。所有实现了这个接口的类都可以在try-with-resources结构中使用。 当try-with-resources结构中抛出一个异常,同时FileInputStreami被关闭时(调用了其close方法)也抛出一个异常,try-with-resources结构中抛出的异常会向外传播,而FileInputStreami被关闭时抛出的异常被抑制了。这与文章开始处利用旧风格代码的例子(在finally语句块中关闭资源)相反。 在JDK7中只要实现了AutoCloseable或Closeable接口的类或接口,都可以使用try-with-resource来实现异常处理和资源关闭。 你可以在块中使用多个资源而且这些资源都能被自动地关闭: ~~~ package com.qunar.lectures.tryResource; import java.io.*; import java.util.ArrayList; import java.util.List; /** * Created by xiaosi on 16-3-4. */ public class TryResourceDemo {    // 获取资源数据    public static List<String> readLines(String resourcePath) {        String path = TryResourceDemo.class.getResource(resourcePath).getPath();        File file = new File(path);        if (!file.exists()) {            throw new RuntimeException("Can not find file + " + resourcePath);        }//if        if (!file.isFile()) {            throw new RuntimeException(resourcePath + " is not a regular file");        }//if        // try-with-resource方式 自动释放资源        try (FileInputStream fis = new FileInputStream(file);             InputStreamReader isr = new InputStreamReader(fis);                BufferedReader br = new BufferedReader(isr)){            List<String> lines = new ArrayList<String>();            String line;            while ((line = br.readLine()) != null) {                lines.add(line);            }//while            return lines;        }        catch (IOException e) {            throw new RuntimeException("Read file failed , file=" + resourcePath, e);        }    }    public static void main(String[] args) {        List<String> lines = readLines("/a.txt");        for(String line : lines){            System.out.println("line:" + line);        }//for    } } ~~~
';

[Java开发之路](19)Long缓存问题

最后更新于:2022-04-01 10:00:02

Long中有个小小的陷阱,就是在-128至127范围内,Long.valueOf(long l)返回的Long的实例是相同的,而在此范围之外每次使用valueOf(long l)时,返回的实例都是不同的。 举例: ~~~ System.out.println(Long.valueOf(-129) == Long.valueOf(-129)); // false System.out.println(Long.valueOf(-128) == Long.valueOf(-128)); // true System.out.println(Long.valueOf(127) == Long.valueOf(127)); // true System.out.println(Long.valueOf(128) == Long.valueOf(128)); // false ~~~ 下面我们通过Long源码进行分析一下: ~~~ public static Long valueOf(long l) { final int offset = 128; if (l >= -128 && l <= 127) { // will cache return LongCache.cache[(int)l + offset]; } return new Long(l); } ~~~ 从上面代码中我们可以看出先判断传递过来的数值是否在[-128,127]之间,如果是则直接从缓存中返回对应的引用,否则新创建一个Long的实例。所以说如果不在这个区间范围内,返回一个新创建的Long类型引用,用==判断就会理所当然的返回false,地址不一样。但是如果我们使用equals方法,则会返回true,数值是一样的。 ~~~ Long.valueOf(128).equals(Long.valueOf(128)) // true ~~~ 我们看看对于在区间范围之内,是如何返回对应的引用?最重要的是Long类中有一个静态的内部类LongCache,专门用于缓存-128至127之间的值。 ~~~ private static class LongCache { private LongCache(){} static final Long cache[] = new Long[-(-128) + 127 + 1]; static { for(int i = 0; i < cache.length; i++) cache[i] = new Long(i - 128); } } ~~~ 在LongCache类中定义了一个cache数组,来存储缓存数据。我们可以看到cache数组的长度:-(-128) + 127 + 1,很明了的知道缓存数据从-128到127,后面的1代表数字0,一共256个元素。 valueOf这个方法设计比较好的一点是offset,它的初始值设为128,目的就是为了数组下标128处存放0,这样就将正数和负数分隔开。
';

[Java开发之路](18)关于Class.getResource和ClassLoader.getResource的路径问题

最后更新于:2022-04-01 10:00:00

Java中取资源时,经常用到Class.getResource和ClassLoader.getResource。昨天老师讲解题目时候,问我们为什么你们都是在文件前家上"/": ~~~ String path = Resources.class.getResource("/a.txt").getPath(); ~~~ 注:在Resources文件下创建了a.txt文件 我想我反正是试出来的,不使用"/"不行。为了正式解答心中的疑惑,我们正式来看看Resources路径问题。 ### 1\. Class.getResource(String path) ~~~ path不以’/'开头时,默认是从此类所在的包下取资源; path以’/'开头时,则是从ClassPath根下获取; ~~~ ~~~ System.out.println("path:"+Resources.class.getResource("/")); System.out.println("path:"+Resources.class.getResource("")); ~~~ 输出结果: path:file:/home/xiaosi/Study/lectures/target/classes/ path:file:/home/xiaosi/Study/lectures/target/classes/com/qunar/lectures/ ### 2\. ClassLoder.getClassLoder.getResource(String path) ~~~ path不能以’/'开头时; path是从ClassPath根下获取; ~~~ ~~~ System.out.println(r.getClass().getClassLoader().getResource("")); System.out.println(r.getClass().getClassLoader().getResource("/"));//null ~~~ 输出结果: file:/home/xiaosi/Study/lectures/target/classes/ null
';

[Java开发之路](16)学习log4j日志

最后更新于:2022-04-01 09:59:57

### 1. 新建一个Java工程,导入Jar包(log4j-1.2.17.jar) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a88f5f7.jpg) Jar包下载地址:[点击打开链接](http://download.csdn.net/detail/sunnyyoona/9411178) ### 2.配置文件:创建并设置log4j.properties ~~~ # 设置 log4j.rootLogger = debug,stdout,D,E # 输出信息到控制台 log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout # 输出格式 log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss, SSS} method:%l%n%m%n # 输出DEBUG 级别以上的日志到D://WorkSpace/logs/debug.log log4j.appender.D = org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File = D://WorkSpace/logs/debug.log log4j.appender.D.Append = true log4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayout # 打印DEBUG信息格式 log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n # 输出ERROR 级别以上的日志到=D://WorkSpace/logs/error.log log4j.appender.E = org.apache.log4j.DailyRollingFileAppender log4j.appender.E.File =D://WorkSpace/logs/error.log log4j.appender.E.Append = true log4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayout # 打印ERROR信息格式 log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n ~~~ ### 3.使用日志 ~~~ package com.qunar.sjf; import org.apache.log4j.Logger; public class ImportMost { private static Logger logger = Logger.getLogger(ImportMost.class); public static void main(String[] args) { // debug级别的信息 logger.debug("This is a debug"); // info级别的信息 logger.info("This is a info"); // error级别的信息 logger.error("This is a error"); } } ~~~ ### 4.输出信息 4.1 控制台输出信息 <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:852px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:835px"><br/><br/><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[DEBUG] 2016-01-13 20:55:05, 622 method:com.qunar.sjf.ImportMost.main(<span style="color:rgb(0,0,128)"><u>ImportMost.java:10</u></span>)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">This is a debug</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[INFO ] 2016-01-13 20:55:05, 627 method:com.qunar.sjf.ImportMost.main(<span style="color:rgb(0,0,128)"><u>ImportMost.java:12</u></span>)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">This is a info</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[ERROR] 2016-01-13 20:55:05, 628 method:com.qunar.sjf.ImportMost.main(<span style="color:rgb(0,0,128)"><u>ImportMost.java:14</u></span>)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">This is a error</span></div></td></tr></tbody></table> 4.2 后台日志 error.log: 2016-01-13 20:55:05  [ main:6 ] - [ ERROR ]  This is a error debug.log: 2016-01-13 20:55:05  [ main:0 ] - [ DEBUG ]  This is a debug 2016-01-13 20:55:05  [ main:5 ] - [ INFO ]  This is a info 2016-01-13 20:55:05  [ main:6 ] - [ ERROR ]  This is a error ### 5.Logger方法 Logger类提供了多种方法来处理日志活动。 Logger类不允许实例化一个新实例,但它可以通过两个静态方法获得一个 Logger 对象: ~~~ public static Logger getRootLogger(); public static Logger getLogger(String name); public static Logger getLogger(Class clazz); ~~~ 第一个方法返回根日志记录器,第二个方法根据给定额参数name检索日志记录器,第三个方法根据给定的Class对象返回日志记录器。 Logging 方法: 我们得到了一个日志记录器之后,可以使用日志记录器的几种方法来记录消息。 Logger类有专门用于打印日志信息方法。 <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:852px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:409px">方法</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:409px">描述</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">public void debug(Object message)</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">打印使用 Level.DEBUG 消息级别</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">public void error(Object message)</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">打印使用 Level.ERROR 消息级别</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">public void fatal(Object message)</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">打印使用 Level.FATAL 消息级别</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">public void info(Object message)</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">打印使用 Level.INFO 消息级别</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">public void warn(Object message)</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">打印使用 Level.WARN 消息级别</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">public void trace(Object message)</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">打印使用Level.TRACE消息级别</td></tr></tbody></table> 所有的级别定义在org.apache.log4j.Level类中,并且任何上述方法都可以调用如下: ### 6.日志级别 org.apache.log4j.Level类提供以下级别,但也可以通过Level类的子类自定义级别。 | 级别 | 描述 | |-----|-----| | ALL | 最低级别,打开所有日志级别 | | DEBUG | 细粒度信息事件,对应用程序调试最有用 | | ERROR | 错误事件,可能仍然允许应用程序继续运行 | | FATAL | 非常严重的错误事件,这可能导致应用程序中止 | | INFO | 指定能够突出在粗粒度级别的应用程序运行情况的信息的消息 | | OFF | 最高级别,关闭日志记录 | | TRACE | 细粒度比DEBUG更低的信息事件 | | WARN | 具有潜在危害的情况 | 对于标准级别关系如下:ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF。ALL是最低级别,OFF是最高级别。 如果设置日志级别为a,则在记录日志时日志级别b可以启用,必须满足b >= a这一条件。 下面的例子明确指出如何可以过滤所有的DEBUG和INFO消息。这个程序使用记录并执行setLevel(Level.X)方法来设置所需的日志记录级别: ~~~ package com.qunar.sjf; import org.apache.log4j.Level; import org.apache.log4j.Logger; public class ImportMost { private static Logger logger = Logger.getLogger(ImportMost.class); public static void main(String[] args) { // 设置日志记录器级别 logger.setLevel(Level.WARN); // 日志信息 logger.trace("Trace Message!"); logger.debug("Debug Message!"); logger.info("Info Message!"); logger.warn("Warn Message!"); logger.error("Error Message!"); logger.fatal("Fatal Message!"); } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:852px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:835px"><br/><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[WARN ] 2016-01-13 21:36:37, 967 method:com.qunar.sjf.ImportMost.main(<span style="color:rgb(0,0,128)"><u>ImportMost.java:15</u></span>)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Warn Message!</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[ERROR] 2016-01-13 21:36:37, 967 method:com.qunar.sjf.ImportMost.main(<span style="color:rgb(0,0,128)"><u>ImportMost.java:16</u></span>)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Error Message!</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[FATAL] 2016-01-13 21:36:37, 967 method:com.qunar.sjf.ImportMost.main(<span style="color:rgb(0,0,128)"><u>ImportMost.java:17</u></span>)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fatal Message!</span></div></td></tr></tbody></table> ### 7.日志格式化 Apache log4j提供了各种布局对象,每一个对象都可以根据各种布局格式记录数据。在层次结构中的顶级类是抽象类是org.apache.log4j.Layout,是所有其他布局类的基类。由于是抽象类,我们分不能直接使用Layout,而是使用Layout的子类。 (1)DateLayout (2)HTMLLayout (3)PatternLayout (4)SimpleLayout (5)XMLLayout <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:852px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:409px"><strong>方法</strong></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:409px"><strong>描述</strong></td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:1rem">format()</span></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:14px; line-height:22px">将LoggingEvent类中的信息格式化成一行日志。</span></td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:14px; line-height:22px">getContentType()</span></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:14px; line-height:22px">定义日志文件的内容类型,目前在Log4J中只是在SMTPAppender中用到,用于设置发送邮件的邮件内容类型。而Layout本身也只有HTMLLayout实现了它。</span></td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:14px; line-height:22px">getHeader()</span></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:14px; line-height:22px">定义日志文件的头,目前在Log4J中只是在HTMLLayout中实现了它。</span></td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:14px; line-height:22px">getFooter()</span></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:14px; line-height:22px">定义日志文件的尾,目前在Log4J中只是HTMLLayout中实现了它。</span></td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:14px; line-height:22px">ignoresThrowable()</span></td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px"><span style="font-size:14px; line-height:22px">定义当前layout是否处理异常类型。在Log4J中,不支持处理异常类型的有:TTCLayout、PatternLayout、SimpleLayout。</span></td></tr></tbody></table> 7.1 HTMLLayout 如果想生成一个HTML格式的日志文件,可以使用HTMLLayout 布局格式。HTMLLayout类扩展抽象org.apache.log4j.Layout类,并覆盖其基类的 format()方法来提供HTML样式格式。 这提供了以下信息显示: - 生成特定的日志事件之前,从应用程序的开始所经过的时间(Time) - 调用该记录请求的线程的名称(Thread) - 与此记录请求相关联的级别(Level) - 日志记录器(Logger)和记录消息的名称(Message) - 可选程序文件的位置信息,并从其中记录被调用的行号(Category 和 Line) | **方法** | **描述** | |-----|-----| | void setContentType(String) | 设置 HTML 的内容类型,默认为 text/html | | void setLocationInfo(String) | 设置日志事件的位置信息(所在目录,所在行数等)。 | | void setTitle(String) | 设置 HTML 文件的标题,默认为 Log4j Log Messages。 | 实例: ~~~ package com.qunar.log; import org.apache.log4j.Logger; public class HtmlLayoutDemo { // 日志记录器 private static Logger logger = Logger.getLogger(HtmlLayoutDemo.class); public static void main(String[] args) { logger.debug("this is an debug message"); logger.info("this is an info message"); } } ~~~ 配置文件: ~~~ # Define the root logger with appender file log = D://WorkSpace/logs/ log4j.rootLogger = debug, FILE # Define the file appender log4j.appender.FILE=org.apache.log4j.FileAppender log4j.appender.FILE.File=${log}HtmlLayoutDemo.html # Define the layout for file appender log4j.appender.FILE.layout=org.apache.log4j.HTMLLayout log4j.appender.FILE.layout.Title=HTML Layout Demo log4j.appender.FILE.layout.LocationInfo=true ~~~ 日志输出: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a89f7fe.jpg) 7.2 PatternLayout 如果您希望基于某种模式生成特定格式的日志信息,可使用 org.apache.Log4j.PatternLayout 格式化您的日志信息。PatternLayout 继承自抽象类 org.apache.Log4j.Layout,覆盖了其 format() 方法,通过提供的模式,来格式化日志信息。 设置转换模式,默认为** %r [%t] %p %c %x - %m%n**。 下面的表格解释了上面模式中用到的字符,以及所有定制模式时能用到的字符: | **模式字符** | **含义** | |-----|-----| | c | 为输出的日志事件分类,比如对于分类 "a.b.c",模式 %c{2} 会输出 "b.c" 。 | | C | 输出发起记录日志请求的类的全名。比如对于类 "org.apache.xyz.SomeClass",模式 %C{1} 会输出 "SomeClass"。 | | d | 输出记录日志的日期,比如 %d{HH:mm:ss,SSS} 或 %d{dd MMM yyyy HH:mm:ss,SSS}。 | | F | 输出文件名。 | | l | 输出生成日志的调用者的位置信息。 | | L | 输出发起日志请求的行号。 | | m | 输出和日志事件关联的,由应用提供的信息。 | | M | 输出发起日志请求的方法名。 | | n | 输出平台相关的换行符。 | | p | 输出日志事件的优先级。 | | r | 输出从构建布局到生成日志事件所花费的时间,以毫秒为单位。 | | t | 输出生成日志事件的线程名。 | | x | 输出和生成日志事件线程相关的 NDC (嵌套诊断上下文)。 | | X | 该字符后跟 MDC 键,比如 X{clientIP} 会输出保存在 MDC 中键 clientIP 对应的值。 | | % | 百分号, %% 会输出一个 %。 | 缺省情况下,信息保持原样输出。但是借助格式修饰符的帮助,就可调整最小列宽、最大列宽以及对齐。 <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:852px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center"><strong>格式修饰符</strong></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center"><strong>左对齐</strong></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center"><strong>最小宽度</strong></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center"><strong>最大宽度</strong></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center"><strong>含义</strong></td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">%20c</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">否</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">20</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">无</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">如果列名少于 20 个字符,左边使用空格补齐(右对齐)</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">%-20c</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">是</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">20</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">无</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">如果列名少于 20 个字符,右边使用空格补齐(左对齐)。</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">%.30c</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">不适用</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">无</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">30</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">如果列名长于 30 个字符,从开头去除。</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">%20.30c</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">否</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">20</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">30</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">如果列名少于 20 个字符,左边使用空格补齐(右对齐);<br/>如果列名长于 30 个字符,从开头去除。</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">%-20.30c</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">是</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">20</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">30</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187)">如果列名少于 20 个字符,右边使用空格补齐(左对齐);<br/>如果列名长于 30 个字符,从开头去除。</td></tr></tbody></table> 实例: ~~~ package com.qunar.log; import org.apache.log4j.Logger; public class PatternLayoutDemo { // 日志记录器 private static Logger logger = Logger.getLogger(PatternLayoutDemo.class); public static void main(String[] args) { logger.debug("this is an debug message"); logger.info("this is an info message"); } } ~~~ 配置文件: ~~~ log4j.rootLogger = DEBUG, FILE log4j.appender.FILE=org.apache.log4j.FileAppender # 日志存储位置 log4j.appender.FILE.File=D:/WorkSpace/logs/log.out # 追加方式写入文件 log4j.appender.FILE.Append=true # 日志布局方式 log4j.appender.FILE.layout=org.apache.log4j.PatternLayout # 日志格式 log4j.appender.FILE.layout.conversionPattern=%d{yyyy-MM-dd}-%t-%x-%-p-%-10c-%n%m%n ~~~ 日志输出 log.out: 2016-01-16-main--DEBUG-com.qunar.log.PatternLayoutDemo- this is an debug message 2016-01-16-main--INFO-com.qunar.log.PatternLayoutDemo- this is an info message ### 8.日志写到文件 8.1 FileAppender 日志记录到文件中,主要用到FileAppender类。FileAppender继承自WriterAppender。 FileAppender配置: | **属性** | **描述** | |-----|-----| | ImmediateFlush | 默认设置为true,表示所有消息都会被立即输出,设为false则不输出 | | Encoding | 编码格式。它可以使用任何字符编码。默认情况下是特定于平台的编码方案 | | Threshold | 写入文件的日志级别。 | | Filename | 日志文件名称。 | | Append | 默认设置为true,以追加的方式把日志写入文件。 | | BufferedIO | 默认设置为false,表示是否需要写入缓存启用。 | | BufferSize | 默认设置为8KB,如果 bufferedI/O 启用,表示缓冲区的大小, | 实例: ~~~ package com.qunar.log; import org.apache.log4j.Logger; public class PatternLayoutDemo { // 日志记录器 private static Logger logger = Logger.getLogger(PatternLayoutDemo.class); public static void main(String[] args) { logger.debug("this is an debug message"); logger.info("this is an info message"); } } ~~~ 配置文件: ~~~ log4j.rootLogger = DEBUG, FILE log4j.appender.FILE=org.apache.log4j.FileAppender # 日志存储位置 log4j.appender.FILE.File=D:/WorkSpace/logs/log.out # 表示所有消息都会被立即输出,设为false则不输出 log4j.appender.FILE.ImmediateFlush=true # 写入的日志级别 log4j.appender.FILE.Threshold=info # 追加方式写入文件 log4j.appender.FILE.Append=true # 日志布局方式 log4j.appender.FILE.layout=org.apache.log4j.PatternLayout # 日志格式 log4j.appender.FILE.layout.conversionPattern=%d{yyyy-MM-dd}-%t-%x-%-p-%-10c-%n%m%n ~~~ 日志输出(注意:只有输出info信息  与配置文件设置有关): 2016-01-16-main--INFO-com.qunar.log.PatternLayoutDemo- this is an info message 8.2 RollingFileAppender 当想要写日志信息转化多个文件要求一样,例如,如果文件大小达到一定的阈值等。 写日志记录信息分成多个文件,必须扩展FileAppender类,并继承其所有属性org.apache.log4j.RollingFileAppender类。有以下除了已如上所述为 FileAppender 可配置参数: | **属性** | **描述** | |-----|-----| | maxFileSize | 默认值是10MB,文件的回滚临界尺寸。 | | maxBackupIndex | 默认值是1,创建的备份文件的数量。 | 实例: ~~~ package com.qunar.log; import org.apache.log4j.Logger; public class PatternLayoutDemo { // 日志记录器 private static Logger logger = Logger.getLogger(PatternLayoutDemo.class); public static void main(String[] args) { for(int i = 0;i < 15;++i){ logger.debug("this is an debug message:" + i); }//for } } ~~~ 配置文件: ~~~ log4j.rootLogger = DEBUG, FILE log4j.appender.FILE=org.apache.log4j.RollingFileAppender # 日志存储位置 log4j.appender.FILE.File=D:/WorkSpace/logs/log.out # 日志回滚最大值 log4j.appender.FILE.MaxFileSize=1KB # 日志文件备份个数 log4j.appender.FILE.MaxBackupIndex=1 # 日志布局方式 log4j.appender.FILE.layout=org.apache.log4j.PatternLayout # 日志格式 log4j.appender.FILE.layout.conversionPattern=%d{yyyy-MM-dd}-%t-%x-%-p-%-10c-%n%m%n ~~~ 此示例配置说明每个日志文件的最大值为1KB。最开始创建日志文件log.out,当超过日志文件最大值时,log.out.1新的日志文件将被创建。同时,log.out中的日志转移到log.out.1中(备份文件设置为1)。log.out日志文件永远写入最新日志。 日志输出: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a8b0990.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a8c0c9c.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a8d0b18.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a8e54c1.jpg) 8.3 DailyRollingFileAppender 如果想它能够按一定的时间频率滚动日志记录文件,以保持日志记录信息的良好记录,就必须它扩展FileAppender类,并继承其所有属性useorg.apache.log4j.DailyRollingFileAppender类。 在DailyRollingFileAppender中可以指定monthly(每月)、 weekly(每周)、daily(每天)、half-daily(每半天)、hourly(每小时)和minutely(每分钟)六个频度,这是通过为 DatePattern选项赋予不同的值来完成的。DatePattern选项的有效值为: <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:852px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:409px"><span style="text-align:start"><strong>DatePattern</strong></span><strong>属性</strong></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:409px"><strong>描述</strong></td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">'.'yyyy-MM</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">对应monthly(每月)</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">'.'yyyy-ww</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">对应weekly(每周)</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">'.'yyyy-MM-dd</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">对应daily(每天)</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">'.'yyyy-MM-dd-a</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">对应half-daily(每半天)</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">'.'yyyy-MM-dd-HH</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">对应hourly(每小时)</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">'.'yyyy-MM-dd-HH-mm</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:409px">对应minutely(每分钟)</td></tr></tbody></table> DatePattern中不用处理的文字要放到单引号(')中,如上面的(**.**)。如果您对此有疑问可以查阅SimpleDateFormat的文档。DailyRollingFileAppender中使用这个类来处理DatePattern。 日志文件输出结果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a903b26.jpg) ### 9.日志输出到数据库中 log4j API提供 org.apache.log4j.jdbc.JDBCAppender 对象,它能够将日志信息在指定的数据库。 | **属性** | **含义** | |-----|-----| | driver | 设置驱动程序类为指定的字符串。如果没有指定驱动程序类,默认为sun.jdbc.odbc.JdbcOdbcDriver | | url | 设置JDBC URL | | layout | 设置要使用的布局。默认布局org.apache.log4j.PatternLayout | | user | 数据库用户名 | | password | 数据库密码 | | sql | 指定SQL语句在每次记录事件发生的时间执行。这可能是INSERT,UPDATE或DELETE | | bufferSize | 设置缓冲区的大小。默认大小为1 | 数据库设置: 创建存储日志的表: ~~~ CREATE TABLE LOGS (ID VARCHAR(20) NOT NULL, TIME DATE NOT NULL, LOGGER VARCHAR(50) NOT NULL, LEVEL VARCHAR(10) NOT NULL, MESSAGE VARCHAR(1000) NOT NULL ); ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a914660.jpg) 日志配置文件: ~~~ log4j.rootLogger = DEBUG, DB log4j.appender.DB=org.apache.log4j.jdbc.JDBCAppender # url链接 log4j.appender.DB.URL=jdbc:mysql://localhost/test # 驱动 log4j.appender.DB.driver=com.mysql.jdbc.Driver # 用户名 log4j.appender.DB.user=root # 密码 log4j.appender.DB.password=root # 日志插入数据库 %d 日期 %C 类名 %p 优先级 %m 日志信息 log4j.appender.DB.sql=INSERT INTO LOGS VALUES('%t','%d{yyyy-MM-dd}','%C','%p','%m') # 日志布局方式 log4j.appender.DB.layout=org.apache.log4j.PatternLayout ~~~ 程序文件: ~~~ package com.qunar.log; import org.apache.log4j.Logger; public class JDBCAppenderDemo { // 日志记录器 private static Logger logger = Logger.getLogger(JDBCAppenderDemo.class); public static void main(String[] args) { for(int i = 0;i < 5;++i){ logger.debug("this is an debug message:" + i); }//for } } ~~~ 必须添加mysql的驱动jar包:[点击打开链接](http://download.csdn.net/detail/sunnyyoona/9412989) 运行结果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a929445.jpg)
';

[Java开发之路](15)注解

最后更新于:2022-04-01 09:59:55

### 1.简介 注解(也被称为元数据),为我们在代码中添加信息提供了一种形式化的方法。注解在一定程度上是把元数据与源代码文件结合在一起,而不是保存在外部文档中这一大趋势之下所催生的。 它可以提供用来完整的描述程序所需的信息,而这些信息是无法使用Java来表达的。因此,注解使得我们能够以将编译器来测试和验证的格式,存储有关程序的额外信息。注解可以用来生成描述符文件,甚至是新的类定义。通过使用注解,我们可以将这些元数据保存在Java源代码中,并利用Annotation API为自己的注解构造处理工具。 注解可以生成更加干净易读的代码以及编译器类型检查等等。 注解(annotation)实在实际的源代码级别保存所有的信息,而不是某种注释性文字(comment),这使得代码更加简洁,便于维护。 ### 2.注解分类 | 按照运行机制分类 | 描述 | |-----|-----| | 源码注解 | 注解只在源码中存在,编译成.class文件就不存在了 | | 编译时注解 | 注解只在源码和.class文件中都存在(例如:@override) | | 运行时注解 | 在运行阶段还起作用,甚至影响运行逻辑的注解(例如:@Autowired) | ### 3.内置注解: (1)@override 表示当前的方法定义将覆盖超类中的方法。如果你不小心拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。 (2)@Deprecated 如果程序员使用了注解为它的元素,那么编译器会发出警告信息。 (3)@SuppressWarnings 关闭不当的编译器警告信息(在java SE5 之前,也可以使用这个注解,不过被忽略不起作用) ### 4.基本语法 4.1 定义注解 可以看到注解的定义很像接口的定义。事实上,与其他任何Java接口一样,注解也会被编译成class文件。 ~~~ package com.qunar.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class Annotation { // 定义Description注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented // 使用@interface 关键字定义注解 public @interface Description{ // 成员以无参无异常方式声明 String desc(); String author(); // 可以使用default关键字为成员指定一个默认值 int age() default 18; } } ~~~ 除了@符号以外,@Description的定义很像一个接口。定义注解的时候会需要一些元注解,如@Target和@Retention。@Target用来定义你的注解将用于什么地方(是一个方法上还是一个类上),@Retention用来定义该注解在哪一个级别上可用(在源代码上或者是类文件上或者是运行时),具体下面讲解。 4.2 注解元素 注解@Description中包含int元素age,以及String元素desc和author。注解元素可以使用的类型如下: - 所有基本数据类型(int,float,boolean等) - String - Class - enum - Annotation - 以上类型的数组 如果你使用了其他类型,那么编译器就会报错。注意,也不允许使用任何包装类型,不过由于自动打包的存在,这算不上什么限制。注解也可以作为元素的类型,也就是注解可以嵌套。 4.3 默认值限制 编译器对元素的默认值有些过分的挑剔。 首先,元素不能有不确定的值,也就是说元素必须要么有默认值,要么使用注解时提供元素的值。 其次,对于非基本类型的元素,无论是在源代码中声明时,或者是在注解接口中定义默认值时,都不能以null作为其值。为了这个约束,我们只能自己定义一些特殊的值,例如空字符串或者负数,来表示某个元素不存在。 4.4 元注解 元注解只负责注解其他的注解。 <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:892px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:206px">元注解</td><td colspan="2" rowspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:429px"><span style="font-size:10.5pt; line-height:1.5">参数</span></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:206px">描述</td></tr><tr><td colspan="1" rowspan="7" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px"><br/><br/><br/>@Taget<br/><br/><br/></td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">CONSTRUCTOR</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">构造器的声明</td><td colspan="1" rowspan="6" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px"><br/><br/>           表示注解可以用于什么地方<br/><br/><br/></td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">FIELD</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">域声明</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">METHOD</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">方法声明</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">PACKAGE</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">包声明</td></tr><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">PARAMETER</td><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">参数声明</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">TYPE</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">类,接口或enum声明</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">LOCAL_VARIABLE</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">局部变量声明</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px"> </td></tr><tr><td colspan="1" rowspan="3" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px"><br/>@Retention<br/></td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">SOURCE</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">注解只在源码中存在,编译成.class文件就不存在了</td><td colspan="1" rowspan="3" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px"><br/>        表示需要在什么级别保存该注解信息<br/></td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">CLASS</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">注解只会在.class文件存在,会被VM丢弃</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">RUNTIME</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">@Document</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px"> </td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px"> </td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">将此注解包含在Javadoc中</td></tr><tr><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">@Inherited</td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px"> </td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px"> </td><td colspan="1" style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:206px">允许子类继承父类中的注解</td></tr></tbody></table> 4.5 使用注解 语法:@<注解名称>(<成员名1> = <成员值1>,<成员名2> = <成员值2>,...) ~~~ package com.qunar.annotation; import com.qunar.annotation.Annotation.Description; public class Student { private String name; @Description(desc = "set name for student object" , author = "sjf0115") public String getName() { return name; } @Description(desc = "get name from student object" , author = "sjf0115", time = "2016-01-11") public void setName(String name) { this.name = name; } } ~~~ ### 5.解析注解 通过反射机制获取类,函数或者成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。 ~~~ package com.qunar.annotation; import java.lang.reflect.Method; import com.qunar.annotation.Annotation.Description; public class ParseAnnotation { public static void main(String[] args){ Class<?> class1 = null; try { // 使用类加载器加载类 class1 = Class.forName("com.qunar.annotation.Student"); } catch (ClassNotFoundException e) { e.printStackTrace(); } // 判断Student类上是否有Description注解 boolean isExits = class1.isAnnotationPresent(Description.class); if(isExits){ // 注解实例 Description desc = class1.getAnnotation(Description.class); System.out.println("注解:" + desc.toString()); }//if // 获取Student类上的所有方法 Method[] methods = class1.getMethods(); // 遍历所有方法 for (Method method : methods) { // 判断方法上是否有Description注解 isExits = method.isAnnotationPresent(Description.class); if(isExits){ Description description = method.getAnnotation(Description.class); System.out.println("方法注解:" + description.toString()); }//if }//for } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:892px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:875px"><br/><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">方法注解:@com.qunar.annotation.Annotation$Description(time=2016-01-12, desc=set name for student object, author=sjf0115)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">方法注解:@com.qunar.annotation.Annotation$Description(time=2016-01-11, desc=get name from student object, author=sjf0115)</span></div></td></tr></tbody></table> ~~~ package com.qunar.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import com.qunar.annotation.Annotation.Description; public class ParseAnnotation { public static void main(String[] args){ Class<?> class1 = null; try { // 使用类加载器加载类 class1 = Class.forName("com.qunar.annotation.Student"); } catch (ClassNotFoundException e) { e.printStackTrace(); } // 判断Student类上是否有Description注解 boolean isExits = class1.isAnnotationPresent(Description.class); if(isExits){ // 注解实例 Description desc = class1.getAnnotation(Description.class); System.out.println("注解:" + desc.toString()); }//if // 获取Student类上的所有方法 Method[] methods = class1.getMethods(); // 遍历所有方法 for (Method method : methods) { // 方法上获取所有的注解 Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { if(annotation instanceof Description){ System.out.println("Description注解:" + annotation.toString()); }//if }//for }//for } } ~~~ 这两个程序都用到了反射的方法:getMethods()和getAnnotation(),它们都属于AnnotatedElement接口(Class,Method与Field等类都实现了该接口)。getAnnotation()方法返回指定类型的注解对象,在这里就是Description。如果被注解的方法上没有该类型的注解,则返回null值。
';

[Java开发之路](14)反射机制

最后更新于:2022-04-01 09:59:53

### 1.Class类 普通对象构造方式: ~~~ // 创建Book实例对象 Book book = new Book(); ~~~ 对于Class的实例对象如何构造呢? Class的构造函数是私有的,只有JVM才能创建实例对象 ~~~ // Class的构造函数是私有的,只有JVM才能创建Class实例对象 Class class1 = new Class(); // 错误 ~~~ ~~~ public final class Class<T> implements java.io.Serializable, java.lang.reflect.GenericDeclaration, java.lang.reflect.Type, java.lang.reflect.AnnotatedElement { /* * Constructor. Only the Java Virtual Machine creates Class * objects. */ private Class() {} .... } ~~~ Class有三种表示方式: (1)XXX.class  XXX为类名   实际再告诉我们任何一个类都有一个隐含的已经太成员变量class ~~~ Class class1 = Book.class; ~~~ (2)XXX.getClass()  XXX为对象名称 已知该类的实例对象,通过getClass()方法获取 ~~~ Book book = new Book(); Class class2 = book.getClass(); ~~~ (3)通过Class类的forName方法获取   ~~~ try { Class class3 = Class.forName("com.qunar.bean.Book"); System.out.println(class1 == class2); System.out.println(class1 == class3); } catch (ClassNotFoundException e) { e.printStackTrace(); } ~~~ 我们完全可以通过类的类类型创建该类的对象实例,通过class1,class2以及class3创建Book的实例 ~~~ try { Class class3 = Class.forName("com.qunar.bean.Book"); // 通过类类型的newInstance方法创建实例对象 Book book2 = (Book)class3.newInstance(); book2.setPrice("23.4"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } ~~~ ### 2.Class动态加载类 Class.forName("类的全称") 不仅表示了类的类类型,还代表了动态加载类。 编译时刻加载类是静态加载类,运行时刻加载类是动态加载类。 ### 3.反射 反射机制--用来检查可用的方法,并返回方法名。 人们想要在运行时获取类的信息的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力。这被称为远程方法调用,它允许一个Java程序将对象分布到多台机器上。 class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Filed,Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与method对象关联的方法。另外,还可以调用getFields(),getMethods()和getConstructors()等很便利的方法,以返回表示字段,方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。 其实,反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,JVM只知道简单的检查这个对象,看它属于哪个特定的类。在用它做其他事情之前,我们必须先加载这个类的class对象。因此,那个类的.class文件对于JVM来说必须是可获取的,要么在本地机器上,要么可以通过网络可以获得。对于反射机制而言,在编译时不能取得.class文件,只能在运行时打开和检查.class文件。 3.1 获取方法信息 ~~~ package com.qunar.reflect; public class ReflectDemo { public static void main(String[] args) { // int的类类型 Class class1 = int.class; Class class2 = String.class; Class class3 = double.class; Class class4 = Double.class; Class class5 = void.class; System.out.println("class1->" + class1.getName()); System.out.println("class2->" + class2.getName()); System.out.println("class3->" + class3.getName()); System.out.println("class4->" + class4.getName()); System.out.println("class5->" + class5.getName()); } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:892px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:875px"><br/><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">class1-&gt;int</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">class2-&gt;java.lang.String</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">class3-&gt;double</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">class4-&gt;java.lang.Double</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">class5-&gt;void</span></div></td></tr></tbody></table> ~~~ // 只打印简单名称(不包含包名称) System.out.println("class2->" + class2.getSimpleName()); // String ~~~ ~~~ /** * 打印类成员方法信息(public函数,包括父类继承而来的) * @param object */ public static void PrintClassPublicFunction(Object object){ // 获取类的信息,首先获取类的类类型 // 传递的是哪个子类的对象 c 就是该子类的类类型 Class c = object.getClass(); System.out.println("类的全称是:" + c.getName()); // 一个成员方法就是一个method对象 // getMethods方法是获取的是所有public的函数,包括父类继承而来的 Method[] methods = c.getMethods(); for (Method method : methods) { // 获取方法返回值类型的类类型 Class returnType = method.getReturnType(); System.out.print(returnType.getName() + " "); // 获取方法的名称 System.out.print(method.getName() + "("); // 获取方法参数 // 得到方法参数列表中类型的类类型 Class[] paramTypes = method.getParameterTypes(); int size = paramTypes.length; for (int i = 0;i < size;++i) { if(i != 0){ System.out.print(","); }//if System.out.print(paramTypes[i].getName()); }//for System.out.println(")"); }//for } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:892px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:875px"><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">boolean startsWith(java.lang.String)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">boolean startsWith(java.lang.String,int)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.CharSequence subSequence(int,int)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String substring(int,int)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String substring(int)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[C toCharArray()</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String toLowerCase(java.util.Locale)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String toLowerCase()</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String toUpperCase()</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String valueOf([C)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.Class getClass()</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">void notify()</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">void notifyAll()</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">void wait(long)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">void wait(long,int)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">void wait()</span></div>....</td></tr></tbody></table> 3.2 获取成员变量信息 ~~~ /** * 打印类成员变量信息 * @param object */ public static void PrintClassFiled(Object object){ // 获取类的信息,首先获取类的类类型 // 传递的是哪个子类的对象 c 就是该子类的类类型 Class c = object.getClass(); /* 成员变量也是对象,java.lang.reflect.Field 类封装了关于成员变量的操作 * getFields()方法获取的是所有的public的成员变量的信息 * getDeclaredFields()获取的是该类自己声明的成员变量的信息 */ Field[] fields = c.getDeclaredFields(); for (Field field : fields) { // 得到成员变量的类型的类类型 Class fieldType = field.getType(); // 得到成员变量的类型 System.out.print(fieldType.getName() + " "); // 得到成员变量的名称 System.out.println(field.getName()); }//for } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:892px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:875px"><br/><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">int MIN_VALUE</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">int MAX_VALUE</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.Class TYPE</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[C digits</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[C DigitTens</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[C DigitOnes</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">[I sizeTable</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">int value</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">int SIZE</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">long serialVersionUID</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">boolean $assertionsDisabled</span></div></td></tr></tbody></table> 3.3 获取构造函数信息 ~~~ /** * 打印类构造函数信息 * @param object */ public static void PrintClassConstructor(Object object){ // 获取类的信息,首先获取类的类类型 // 传递的是哪个子类的对象 c 就是该子类的类类型 Class c = object.getClass(); /* 构造函数也是对象,java.lang.reflect.Constructor 类封装了关于构造函数的操作 * getConstructors()方法获取的是所有的public的构造函数的信息 * getDeclaredConstructors()获取的是该类自己声明的构造函数的信息 */ Constructor[] constructors = c.getConstructors(); for (Constructor constructor : constructors) { // 构造函数的名称 System.out.print(constructor.getName() + "("); // 获取构造函数的参数列表,得到的是参数列表的类类型 Class[] paramTypes = constructor.getParameterTypes(); int size = paramTypes.length; for(int i = 0;i < size;++i){ if(i != 0){ System.out.print(","); }//if // 得到参数名称 System.out.print(paramTypes[i].getName()); }//for System.out.println(")"); }//for } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:892px"><tbody><tr><td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:875px"><br/><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([B)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([B,int,int)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([B,java.nio.charset.Charset)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([B,java.lang.String)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([B,int,int,java.nio.charset.Charset)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String(java.lang.StringBuilder)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String(java.lang.StringBuffer)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([I,int,int)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([C,int,int)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([C)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String(java.lang.String)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String()</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([B,int,int,java.lang.String)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([B,int)</span></div><div style="margin:0px"><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">java.lang.String([B,int,int,int)</span></div></td></tr></tbody></table> 3.4 方法的反射 如何获取某个方法?方法的名称和方法的参数列表才能唯一决定某个方法。 如何进行操作?通过method.invoke(对象,参数列表) ~~~ package com.qunar.reflect; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import com.qunar.bean.Calculates; public class ReflectDemo { public static void main(String[] args) { // 获取类的信息,首先获取类的类类型 Calculates calculates = new Calculates(); calculates.setNum1(20); calculates.setNum2(40); Class c = calculates.getClass(); /* getMethod()方法获取的是public的方法信息 * getDeclaredMethod()获取的是该类自己声明的方法的信息 */ try { // 获取方法 名称和参数列表共同决定 // Method method = c.getDeclaredMethod("add", new Class[]{int.class,int.class}); Method method = c.getDeclaredMethod("add", int.class,int.class); // 方法的反射 // 对于calculates.add(10,40)来说,方法的反射操作是用method方法调用 和 calculates.add(10,40)的效果一样 // int result = (int)method.invoke(calculates, new Object[]{10,40}); int result = (int)method.invoke(calculates, 10,40); System.out.println(result); // 对于没有参数的方法 Method method2 = c.getDeclaredMethod("print"); method2.invoke(calculates); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } ~~~
';

[Java开发之路](12)JDOM和DOM4J解析XML文档

最后更新于:2022-04-01 09:59:50

### 1.JDOM解析XML文档 1.1 简介 JDOM是一个开源项目,它基于树型结构,利用纯JAVA的技术对XML文档实现解析、生成、序列化以及多种操作。JDOM 直接为JAVA编程服务。它利用更为强有力的JAVA语言的诸多特性(方法重载、集合概念以及映射),把SAX和DOM的功能有效地结合起来。在使用设计上尽可能地隐藏原来使用XML过程中的复杂性。利用JDOM处理XML文档将是一件轻松、简单的事。 JDOM帮助文档 : [http://www.jdom.org/docs/apidocs/](http://www.jdom.org/docs/apidocs/) Jar包下载地址:[点击打开链接](http://download.csdn.net/detail/sunnyyoona/9387707) 1.2 解析步骤 (1)创建SAXBuilder对象 ~~~ SAXBuilder saxBuilder = new SAXBuilder(); ~~~ (2)创建输入流对象并将XML文档加载到输入流中 ~~~ FileInputStream inputStream = new FileInputStream("D:\\bookstore.xml"); ~~~ (3)通过saxBuilder对象的build方法将输入流加载到saxBuilder中(注意:Document 所引用的包是org.jdom2.Document;) ~~~ Document document = saxBuilder.build(inputStream); ~~~ (4)通过document对象获取XML文档的根节点 ~~~ Element rootElement = document.getRootElement(); ~~~ (5)根据根节点获取根节点下的子节点集合 ~~~ List<Element> bookList = rootElement.getChildren(); ~~~ (6)根据节点获取属性节点集合 ~~~ List<Attribute> attrList = book.getAttributes(); ~~~ (7)根据节点(元素节点或者属性节点)获取节点名称和节点值 ~~~ // 属性名称 node.getName(); // 属性值 node.getValue(); ~~~ 1.3 主要方法 (1)返回文档的根节点 public Element getRootElement() ~~~ // document为Document对象 Element rootElement = document.getRootElement(); ~~~ (2)返回节点的所有子节点的集合 public java.util.List<Element> getChildren() ~~~ // rootElement为Element对象 List<Element> bookList = rootElement.getChildren(); ~~~ (3)返回节点的所有属性节点的集合 public java.util.List<Attribute> getAttributes() ~~~ // book为Element对象 List<Attribute> attrList = book.getAttributes() ~~~ (4)根据子节点的名称返回节点的子节点 public Element getChild(java.lang.String cname) ~~~ Element titleElement = book.getChild("title"); ~~~ (5)返回节点的名称 public java.lang.String getName() ~~~ // titleElement为title节点 titleElement.getName(); ~~~ (6)返回节点值 public java.lang.String getValue()、 ~~~ titleElement.getValue() ~~~ DOM方式getNodeValue() 对于元素节点时返回null。不同于DOM方式,JDOM无论是属性节点还是元素节点都会返回节点对应的文本值。 ~~~ <author>Scott Meyers</author> ~~~ 对于这个节点来说,DOM的getNodeValue()返回null,JDOM的getValue()返回"Scott Meyers"。 1.3 具体实例 ~~~ package com.qunar.xml; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; import org.jdom2.Attribute; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; /** * JDOM解析XML文档 * @author sjf0115 * */ public class JDOMXMLCode { public static void main(String[] args) { try { // 创建SAXBuilder对象 SAXBuilder saxBuilder = new SAXBuilder(); // 创建输入流对象并将XML文档加载到输入流中 FileInputStream inputStream = new FileInputStream("D:\\bookstore.xml"); // 通过saxBuilder对象的build方法将输入流加载到saxBuilder中 Document document = saxBuilder.build(inputStream); // 通过document对象获取XML文档的根节点 Element rootElement = document.getRootElement(); // 根据根节点获取根节点下的子节点集合 List<Element> bookList = rootElement.getChildren(); // 遍历子节点 for (Element book : bookList) { System.out.println("开始解析一本书..."); // 解析属性 List<Attribute> attrList = book.getAttributes(); for (Attribute attribute : attrList) { // 属性名称 System.out.print("---" + attribute.getName() + ":"); // 属性值 System.out.println(attribute.getValue()); }//for // 获取book节点下的子节点 List<Element> bookChildren = book.getChildren(); for (Element bookChild : bookChildren) { // 节点名称 System.out.print("------" + bookChild.getName() + ":"); // 节点值 System.out.println(bookChild.getValue()); }//for System.out.println("结束解析一本书..."); }//for } catch (FileNotFoundException e) { e.printStackTrace(); } catch (JDOMException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ **运行结果:** <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:942px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:941px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">---category:Java</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------title:Java多线程编程核心技术</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------author:高洪岩</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------year:2015</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------price:69.00</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">---category:C++</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------title:Effective C++: 55 Specific Ways to Improve Your Programs and Designs</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------author:Scott Meyers</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------year:2006</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------price:58.00</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">---category:Web</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------title:Learning XML</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------author:Erik T. Ray</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------year:2016</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------price:39.95</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析一本书...</span></div></td></tr></tbody></table> ### 2.DOM4J解析XML文档 2.1 简介 DOM4J是一个Java的XML API,类似于JDOM,用来读写XML文件的。DOM4J是一个十分优秀的JavaXML API,具有性能优异、功能强大和极其易使用的特点,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。如今可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J。这已经是必须使用的jar包, Hibernate也用它来读写配置文件。 Jar包下载地址:[点击打开链接](http://download.csdn.net/detail/sunnyyoona/9388272) 2.2解析 (1)创建SAXReader对象 ~~~ SAXReader saxReader = new SAXReader(); ~~~ (2)通过SaxReader对象的read方法加载XML文档获取Document对象 ~~~ Document document = saxReader.read(new File("D:\\bookstore.xml")); ~~~ (3)通过Document对象获取根节点 ~~~ Element bookstore = document.getRootElement(); ~~~ (4)通过Element对象的elementIterator方法获取迭代器 ~~~ Iterator iterator = bookstore.elementIterator(); ~~~ (5)遍历迭代器获取根节点的信息 ~~~ while (iterator.hasNext()) { // 获取下一个子节点 Element book = (Element)iterator.next(); } ~~~ (6)获取属性节点的属性名称和属性值 ~~~ // 获取book的属性节点集合 List<Attribute> bookAttr = book.attributes(); // 遍历book属性节点 for (Attribute attribute : bookAttr) { // 获取book的属性节点的属性名称以及属性值 System.out.println("name:" + attribute.getName() + " value:" + attribute.getValue()); }//for ~~~ (7)获取元素节点的节点名称和对应的文本值 ~~~ Iterator ite = book.elementIterator(); // 遍历book节点的子节点 while(ite.hasNext()){ // book节点的子节点 Element bookChild = (Element)ite.next(); System.out.println("name:" + bookChild.getName() + " value:" + bookChild.getStringValue()); //System.out.println("name:" + bookChild.getName() + " value:" + bookChild.getText()); }//while ~~~ 2.3 具体案例 ~~~ package com.qunar.xml; import java.io.File; import java.util.Iterator; import java.util.List; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class DOM4JXMLCode { public static void main(String[] args) { try { // 创建SAXReader对象 SAXReader saxReader = new SAXReader(); // 通过SaxReader对象的read方法加载XML文档获取Document对象 Document document = saxReader.read(new File("D:\\bookstore.xml")); // 通过Document对象获取根节点 Element bookstore = document.getRootElement(); // 通过Element对象的elementIterator方法获取迭代器 Iterator iterator = bookstore.elementIterator(); // 遍历根节点的子节点 while (iterator.hasNext()) { System.out.println("开始解析一本书..."); // 获取下一个元素节点 Element book = (Element)iterator.next(); // 获取book的属性节点集合 List<Attribute> bookAttr = book.attributes(); // 遍历book属性节点 for (Attribute attribute : bookAttr) { // 获取book的属性节点的属性名称以及属性值 System.out.println("name:" + attribute.getName() + " value:" + attribute.getValue()); }//for Iterator ite = book.elementIterator(); // 遍历book节点的子节点 while(ite.hasNext()){ // book节点的子节点 Element bookChild = (Element)ite.next(); System.out.println("name:" + bookChild.getName() + " value:" + bookChild.getStringValue()); //System.out.println("name:" + bookChild.getName() + " value:" + bookChild.getText()); }//while System.out.println("结束解析一本书..."); } } catch (DocumentException e) { e.printStackTrace(); } } } ~~~ **运行结果:** <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:942px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:941px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:category   value:Java</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:title   value:Java多线程编程核心技术</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:author   value:高洪岩</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:year   value:2015</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:price   value:69.00</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:category   value:C++</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:title   value:Effective C++: 55 Specific Ways to Improve Your Programs and Designs</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:author   value:Scott Meyers</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:year   value:2006</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:price   value:58.00</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析一本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:category   value:Web</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:title   value:Learning XML</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:author   value:Erik T. Ray</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:year   value:2016</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">name:price   value:39.95</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析一本书...</span></div></td></tr></tbody></table>
';

[Java开发之路](11)SAX解析XML文档

最后更新于:2022-04-01 09:59:48

### 1.简介 Dom解析功能强大,可增删改查,操作时会将XML文档读到内存,因此适用于小文档; SAX解析是从头到尾逐行逐个元素解析,修改较为不便,但适用于只读的大文档; SAX采用事件驱动的方式解析XML。套用网友的解释:如同在电影院看电影一样,从头到尾看一遍,不能回退(Dom可来来回回读取),在看电影的过程中,每遇到一个情节,都会调用大脑去接收处理这些信息。SAX也是相同的原理,每遇到一个元素节点,都会调用相应的方法来处理。在SAX的解析过程中,读取到文档开头、文档结尾,元素的开头和元素结尾都会调用相应方法,我们可以在这些方法中进行相应事件处理。 对应方法: ~~~ public void startDocument() throws SAXException { } public void endDocument() throws SAXException { } public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { } public void endElement(String uri, String localName, String qName) throws SAXException { } ~~~ 我们还需一个方法来处理元素节点中间的文本节点(我们常误以为元素节点的文本值) ~~~ public void characters(char[] ch, int start, int length) throws SAXException { } ~~~ ### 2.解析 解析步骤: (1)通过SAXParserFactory的静态方法newInstance()方法获取SAXParserFactory实例对象factory ~~~ SAXParserFactory factory = SAXParserFactory.newInstance(); ~~~ (2)通过SAXParserFactory实例的newSAXParser()方法返回SAXParser实例parser ~~~ SAXParser parser = factory.newSAXParser(); ~~~ (3)创建一个类继承DefaultHandler,重写其中的一些方法进行业务处理 ~~~ package com.qunar.handler; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; public class SAXParserHandler extends DefaultHandler{ // 用来标示解析开始 @Override public void startDocument() throws SAXException { } // 用来标示解析结束 @Override public void endDocument() throws SAXException { } // 用来遍历XML文件的开始标签 @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); } // 用来遍历XML文件的结束标签 @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); } @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); } } ~~~ (4)创建Handler类对象实例 ~~~ // 定义SAXParserHandler对象 SAXParserHandler handler = new SAXParserHandler(); ~~~ (5)解析XML文档 ~~~ <?xml version="1.0" encoding="utf-8"?><bookstore> <book category="Java"> <title lang="chi">Java多线程编程核心技术</title> <author>高洪岩</author> <year>2015</year> <price>69.00</price> </book> <book category="C++"> <title lang="en">Effective C++: 55 Specific Ways to Improve Your Programs and Designs</title> <author>Scott Meyers</author> <year>2006</year> <price>58.00</price> </book> <book category="Web"> <title lang="en">Learning XML</title> <author>Erik T. Ray</author> <year>2016</year> <price>39.95</price> </book> </bookstore> ~~~ ### 3.具体实例: ~~~ package com.qunar.handler; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; public class SAXParserHandler extends DefaultHandler{ private int bookIndex = 0; // 用来标示解析开始 @Override public void startDocument() throws SAXException { System.out.println("SAX解析开始..."); } // 用来标示解析结束 @Override public void endDocument() throws SAXException { System.out.println("SAX解析结束..."); } // 用来遍历XML文件的开始标签 @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { // 调用DefaultHandler类的startElement方法 super.startElement(uri, localName, qName, attributes); // 开始解析book元素节点 if(qName.equals("book")){ ++ bookIndex; System.out.println("开始解析第" + bookIndex + "本书..."); // 已知book元素节点下的属性名称,根据属性名称获取属性值 /*String value = attributes.getValue("category"); System.out.println("value->"+value);*/ // 不知道book元素节点下的属性名称以及个数 int size = attributes.getLength(); for(int i = 0;i < size;++i){ System.out.println(attributes.getQName(i) + ":" + attributes.getValue(i)); }//for }//if else if(!qName.equals("bookstore")){ System.out.print(qName + ":"); }//else } // 用来遍历XML文件的结束标签 @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); // 判断一本书是否解析完 if(qName.equals("book")){ System.out.println("结束解析第" + bookIndex + "本书..."); }//if } @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); String text = new String(ch, start, length); if(!text.trim().equals("")){ System.out.println(text); }//if } } ~~~ ~~~ package com.qunar.xml; import java.io.IOException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; import com.qunar.handler.SAXParserHandler; /** * SAX方式解析XML文档 * @author sjf0115 * */ public class SAXXMLCode { public static void main(String[] args) { String path = "D:\\bookstore.xml"; try { // 通过SAXParserFactory的静态方法newInstance()方法获取SAXParserFactory实例对象factory SAXParserFactory factory = SAXParserFactory.newInstance(); // 通过SAXParserFactory实例的newSAXParser()方法返回SAXParser实例parser SAXParser saxParser = factory.newSAXParser(); // 定义SAXParserHandler对象 SAXParserHandler handler = new SAXParserHandler(); // 解析XML文档 saxParser.parse(path, handler); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ **运行结果:** <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1050px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1049px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">SAX解析开始...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析第1本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">category:Java</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">title:Java多线程编程核心技术</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">author:高洪岩</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">year:2015</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">price:69.00</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析第1本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析第2本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">category:C++</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">title:Effective C++: 55 Specific Ways to Improve Your Programs and Designs</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">author:Scott Meyers</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">year:2006</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">price:58.00</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析第2本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析第3本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">category:Web</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">title:Learning XML</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">author:Erik T. Ray</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">year:2016</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">price:39.95</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析第3本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">SAX解析结束...</span></div></td></tr></tbody></table> ### 4.解析并储存于对象中 ~~~ package com.qunar.bean; /** * book实体类 * @author sjf0115 * */ public class Book { private String category; private String title; private String author; private String year; private String price; private String lang; public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getYear() { return year; } public void setYear(String year) { this.year = year; } public String getPrice() { return price; } public void setPrice(String price) { this.price = price; } public String getLang() { return lang; } public void setLang(String lang) { this.lang = lang; } @Override public String toString() { return "category:" + category + " lang:" + lang + " title:" + title + " author:" + author + " year:" + year + " price:" + price; } } ~~~ ~~~ package com.qunar.handler; import java.util.ArrayList; import java.util.List; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import com.qunar.bean.Book; public class SAXParserHandler extends DefaultHandler{ private Book book; private int bookIndex = 0; // 节点文本内容 private String text; private List<Book> bookList = new ArrayList<Book>(); public List<Book> getBookList() { return bookList; } // 用来标示解析开始 @Override public void startDocument() throws SAXException { System.out.println("SAX解析开始..."); } // 用来标示解析结束 @Override public void endDocument() throws SAXException { System.out.println("SAX解析结束..."); } // 用来遍历XML文件的开始标签 @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { // 调用DefaultHandler类的startElement方法 super.startElement(uri, localName, qName, attributes); // 开始解析book元素节点 if(qName.equals("book")){ // 创建一个book对象 book = new Book(); ++ bookIndex; System.out.println("开始解析第" + bookIndex + "本书..."); int size = attributes.getLength(); for(int i = 0;i < size;++i){ String attr = attributes.getQName(i); // 属性category if(attr.equals("category")){ book.setCategory(attributes.getValue(i)); }//if }//for }//if // 用于遍历title节点中的属性 else if(qName.equals("title")){ int size = attributes.getLength(); for(int i = 0;i < size;++i){ String attr = attributes.getQName(i); // 属性category if(attr.equals("lang")){ book.setLang(attributes.getValue(i)); }//if }//for }//else } // 用来遍历XML文件的结束标签 @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); // 判断一本书是否解析完 if(qName.equals("book")){ bookList.add(book); book = null; System.out.println("结束解析第" + bookIndex + "本书..."); }//if else if(qName.equals("title")){ book.setTitle(text); }//else else if(qName.equals("author")){ book.setAuthor(text); }//else else if(qName.equals("year")){ book.setYear(text); }//else else if(qName.equals("price")){ book.setPrice(text); }//else } // 文本值 @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); text = new String(ch, start, length); } } ~~~ ~~~ package com.qunar.xml; import java.io.IOException; import java.util.List; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; import com.qunar.bean.Book; import com.qunar.handler.SAXParserHandler; /** * SAX方式解析XML文档 * @author sjf0115 * */ public class SAXXMLCode { public static void main(String[] args) { String path = "D:\\bookstore.xml"; try { // 通过SAXParserFactory的静态方法newInstance()方法获取SAXParserFactory实例对象factory SAXParserFactory factory = SAXParserFactory.newInstance(); // 通过SAXParserFactory实例的newSAXParser()方法返回SAXParser实例parser SAXParser saxParser = factory.newSAXParser(); // 定义SAXParserHandler对象 SAXParserHandler handler = new SAXParserHandler(); // 解析XML文档 saxParser.parse(path, handler); // 得到遍历结果 List<Book> bookList = handler.getBookList(); System.out.println("遍历结果:"); for (Book book : bookList) { System.out.println(book); }//for } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ **运行结果:** <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1050px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1049px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">SAX解析开始...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析第1本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析第1本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析第2本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析第2本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">开始解析第3本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">结束解析第3本书...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">SAX解析结束...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">遍历结果:</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">category:Java  lang:chi   title:Java多线程编程核心技术   author:高洪岩   year:2015   price:69.00</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">category:C++  lang:en   title:Effective C++: 55 Specific Ways to Improve Your Programs and Designs   author:Scott Meyers   year:2006   price:58.00</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">category:Web  lang:en   title:Learning XML   author:Erik T. Ray   year:2016   price:39.95</span></div></td></tr></tbody></table>
';

[Java开发之路](10)DOM解析XML文档

最后更新于:2022-04-01 09:59:46

对象序列化的一个重要限制是它只是Java的解决方案:只有Java程序才能反序列化这种对象。一种更具操作性的解决方案是将数据转化为XML格式,这可以使其被各种各样的平台和语言使用。 ### 1.简介 DOM 是用与平台和语言无关的方式表示XML文档的官方 W3C 标准。DOM 是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构, 然后才能做任何工作。 由于它是基于信息层次的,因而 DOM 被认为是基于树或基于对象的。DOM 以及广义的基于树的处理具有几个优点。首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。 下面我们介绍一个具体的XML例子。 ~~~ <?xml version="1.0" encoding="utf-8"?> <bookstore> <book category="Java"> <title lang="chi">Java多线程编程核心技术</title> <author>高洪岩</author> <year>2015</year> <price>69.00</price> </book> <book category="C++"> <title lang="en">Effective C++: 55 Specific Ways to Improve Your Programs and Designs</title> <author>Scott Meyers</author> <year>2006</year> <price>58.00</price> </book> <book category="Web"> <title lang="en">Learning XML</title> <author>Erik T. Ray</author> <year>2003</year> <price>39.95</price> </book> </bookstore> ~~~ 在上面的 XML 中,根节点是 <bookstore>。文档中的所有其他节点都被包含在 <bookstore> 中。 根节点 <bookstore> 有三个 <book> 节点。 第一个 <book> 节点有四个节点:<title>, <author>, <year> 以及 <price>,其中每个节点都包含一个文本节点,"Java多线程编程核心技术", "高洪岩", "2015" 以及 "69.00"。 根据 DOM规定,XML 文档中的每个成分都是一个节点。 - 整个文档是一个文档节点 - 每个 XML 标签是一个元素节点 - 包含在 XML 元素中的文本是文本节点 - 每一个 XML 属性是一个属性节点 | **节点类型** | **NodeType** | **Named Constant** | **NodeName的返回值** | **NodeValue的返回值** | |-----|-----|-----|-----|-----| | 元素节点 | 1 | ElEMENT_NODE | 元素节点名称(例如:title) | null | | 属性节点 | 2 | ATTRIBUTE_NODE | 属性名称(例如:category) | 属性值(例如:Java) | | 文本节点 | 3 | TEXT_NODE | #text | 节点内容 | 举例说明: ~~~ <title lang="chi">Java多线程编程核心技术</title> ~~~ 以上可以得到一个元素节点(NodeType=1),调用getNodeName()方法得到节点名称"title",调用getNodeValue()方法得到null,而不是我们想象中的"Java多线程编程核心技术"。从元素节点得到一个子节点---文本节点(NodeType=3),调用getNodeName()方法得到"#text",调用getNodeValue方法得到"Java多线程编程核心技术"。另外,我们也可以通过元素节点得到属性节点(NodeType=2),调用getNodeName()方法得到"lang",调用getNodeValue方法得到"chi"。 **注意:** <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1099px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187)"><br/><span style="color:#ff6820">   文本总是存储在文本节点中</span>。<br/>   在 DOM 处理中一个普遍的<span style="color:#ff6820">错误</span>是,认为<span style="color:#ff6820">元素节点包含文本</span>。实际上元素节点的文本是存储在文本节点中的。<br/>   在上面例子中:&lt;title lang="chi"&gt;Java多线程编程核心技术&lt;/title&gt; ,&lt;title&gt;是一个元素节点 <span style="font-size:10.5pt; line-height:1.5">,并且拥有一个值为 "Java多线程编程核心技术</span><span style="font-size:10.5pt; line-height:1.5">" 的文本节点。不要误认为"</span><span style="font-size:10.5pt; line-height:1.5">Java<br/>   多线程编程核心技术</span><span style="font-size:10.5pt; line-height:1.5">" 是 &lt;</span><span style="font-size:10.5pt; line-height:1.5">title</span><span style="font-size:10.5pt; line-height:1.5">&gt; 元素节点的值。</span></td></tr></tbody></table> ### 2.节点树 DOM 把 XML 文档视为一种树结构(节点树)。我们可以通过这棵树访问所有节点,并且可以修改或删除它们的内容,也可以创建新的元素。节点树展示了节点的集合,以及它们之间的联系。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a86ab08.jpg) ### 3.解析 (1)通过抽象工厂类DocumentBuilderFactory的静态方法newInstance获得一个工厂实例对象 ~~~ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); ~~~ (2)通过工厂实例对象获得一个DocumentBuilder对象 ~~~ DocumentBuilder builder = factory.newDocumentBuilder(); ~~~ (3)通过DocumentBuilder对象的parse方法加载XML文件进行解析转换为Document对象(注意:org.w3c.dom.Document) ~~~ Document document = builder.parse("D:\\bookstore.xml"); ~~~ (4)通过Document对象的getElementsByTagName()方法获得元素节点集合 ~~~ NodeList bookList = document.getElementsByTagName("book"); ~~~ (5)通过元素节点集合获得元素节点 ~~~ Node bookNode = bookList.item(i); ~~~ (6)通过元素节点可以获取元素节点所附属的属性节点集合以及属性节点 ~~~ NamedNodeMap attriMap = bookNode.getAttributes(); // 通过item方法获取元素节点的属性节点 Node attriNode = attriMap.item(j); ~~~ (7)通过节点(元素节点,属性节点,文本节点)获得节点名称和节点值 ~~~ // 获取元素节点类型 node.getNodeType(); // 获取元素节点名称 node.getNodeName(); // 获取元素节点值 node.getNodeValue(); ~~~ 我们解析上面XML文件: ~~~ package com.qunar.xml; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; public class DomXmlCode { public static void main(String[] args){ try { // 创建一个DocumentBuilderFactory对象 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 创建DocumentBuilder对象 DocumentBuilder builder = factory.newDocumentBuilder(); // 解析XML文件获取Document对象(注意:org.w3c.dom.Document) Document document = builder.parse("D:\\bookstore.xml"); // 获取所有book元素节点集合 NodeList bookList = document.getElementsByTagName("book"); // 遍历每一个book元素节点 int size = bookList.getLength(); System.out.println("一共有" + size + "本书籍..."); // 元素节点 for(int i = 0;i < size;++i){ // 通过item方法获取每一个book元素节点 Node bookNode = bookList.item(i); // 获取book元素节点所有属性节点集合 NamedNodeMap attriMap = bookNode.getAttributes(); System.out.println("第" + (i+1) + "本书籍:"); int attriSize = attriMap.getLength(); System.out.println("---共有" + attriSize + "个属性"); // 属性 for(int j = 0;j < attriSize;++j){ // 通过item方法获取元素节点的属性节点 Node attriNode = attriMap.item(j); // 获取属性节点属性类型 System.out.print("------type:" + attriNode.getNodeType()); // 获取属性节点属性名称 System.out.print(" name:" + attriNode.getNodeName()); // 获取属性节点属性值 System.out.println(" value:" + attriNode.getNodeValue()); }//for // 如果知道元素节点有几个属性 /*Element element = (Element)bookList.item(i); String attriValue = element.getAttribute("category"); System.out.println(" value:" + attriValue);*/ // 获取book元素节点的子节点集合 NodeList childNodeList = bookNode.getChildNodes(); int childSize = childNodeList.getLength(); System.out.println("---共有" + childSize + "个子节点(元素节点和文本节点):"); for(int k = 0;k < childSize;++k){ // 获取子节点 Node childNode = childNodeList.item(k); // 区分Elemet类型节点和Text类型节点 if(childNode.getNodeType() == Node.ELEMENT_NODE){ // 获取元素子节点类型 System.out.print("------type:" + childNode.getNodeType()); // 获取元素子节点名称 System.out.print(" name:" + childNode.getNodeName()); // 获取元素子节点值 System.out.print(" value:" + childNode.getNodeValue()); // 我们误以为是子节点的值 其是是子节点的一个文本节点 System.out.print(" (sub-name:" + childNode.getFirstChild().getNodeName()); System.out.print(" sub-type:" + childNode.getFirstChild().getNodeType()); System.out.println(" sub-value:" + childNode.getFirstChild().getNodeValue() + ")"); }//if }//for }//for } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ **运行结果:** <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1099px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">一共有3本书籍...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">第1本书籍:</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">---共有1个属性</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:2   name:category   value:Java</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">---共有9个子节点(元素节点和文本节点):</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:title   value:null   (sub-name:#text   sub-type:3   sub-value:Java多线程编程核心技术)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:author   value:null   (sub-name:#text   sub-type:3   sub-value:高洪岩)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:year   value:null   (sub-name:#text   sub-type:3   sub-value:2015)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:price   value:null   (sub-name:#text   sub-type:3   sub-value:69.00)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">第2本书籍:</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">---共有1个属性</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:2   name:category   value:C++</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">---共有9个子节点(元素节点和文本节点):</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:title   value:null   (sub-name:#text   sub-type:3   sub-value:Effective C++: 55 Specific Ways to Improve Your Programs and Designs)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:author   value:null   (sub-name:#text   sub-type:3   sub-value:Scott Meyers)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:year   value:null   (sub-name:#text   sub-type:3   sub-value:2006)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:price   value:null   (sub-name:#text   sub-type:3   sub-value:58.00)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">第3本书籍:</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">---共有1个属性</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:2   name:category   value:Web</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">---共有9个子节点(元素节点和文本节点):</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:title   value:null   (sub-name:subtitle   sub-type:1   sub-value:null)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:author   value:null   (sub-name:#text   sub-type:3   sub-value:Erik T. Ray)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:year   value:null   (sub-name:#text   sub-type:3   sub-value:2003)</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">------type:1   name:price   value:null   (sub-name:#text   sub-type:3   sub-value:39.95)</span></div></td></tr></tbody></table> **注意:** <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1099px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><br/><div><span style="font-size:14pt; color:rgb(0,0,128); font-family:微软雅黑"><u>com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException<span style="text-decoration:none; color:rgb(255,0,0)">: 1 字节的 UTF-8 序列的字节 1 无效。</span></u></span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.impl.io.UTF8Reader.invalidByte(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.impl.io.UTF8Reader.read(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.load(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.skipChar(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at javax.xml.parsers.DocumentBuilder.parse(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.qunar.xml.DomXmlCode.main(<span style="color:rgb(0,0,128)"><u>DomXmlCode.java:24</u></span>)</span></div><br/>这个问题的主要原因是xml文件中声明的编码与xml文件本身保存时的编码不一致。比如你的声明是&lt;?xml version="1.0" encoding="UTF-8"?&gt; 但是却以ANSI格式编码保存,尽管并没有乱码出现,但是xml解析器是无法解析的。解决办法就是重新设置xml文件保存时的编码与声明的一致</td></tr></tbody></table> <title lang="chi">Java多线程编程核心技术</title> 被视为一个元素节点(childNode),之所以元素节点值返回null,是因为"Java多线程编程核心技术"被视为该元素节点的一个子节点,而不是我们误以为的元素值。我们要得到这本文本值,使用一下代码: ~~~ childNode.getFirstChild().getNodeName() ~~~ 我们也可以: ~~~ childNode.getTextContent() ~~~ 但是以上两者还是有一定的区别: 对于一下XML解析时: ~~~ <title lang="en"><subtitle>XML</subtitle>Learning XML</title> ~~~ 第一种方法(getFirstChild)会得到null,而第二种方法会得到XML Learning XML。 ### 4.添加节点 (1)通过Document对象创建一个节点 ~~~ Element bookNode = document.createElement("book"); ~~~ (2)为节点设置属性 ~~~ // 添加属性 bookNode.setAttribute("category", "Java"); ~~~ 还有另外一种方法,先创建一个属性节点,然后为元素节点设置属性节点 ~~~ // 添加节点title Element titleNode = document.createElement("title"); // 添加属性(第二种方法:先创建一个属性节点,设置名称和值) Attr langAttr = document.createAttribute("lang"); langAttr.setNodeValue("chi"); titleNode.setAttributeNode(langAttr); ~~~ (3)把创建的节点添加到根节点中 ~~~ // 获取根节点 Element rootNode = document.getDocumentElement(); // bookNode节点插入节点树中 rootNode.appendChild(bookNode); ~~~ 具体实例: ~~~ package com.qunar.xml; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; public class DomXmlCode { public static void outputToXml(Node node, String filename) { try { TransformerFactory transFactory = TransformerFactory.newInstance(); Transformer transformer = transFactory.newTransformer(); // 设置各种输出属性 transformer.setOutputProperty("encoding", "utf-8"); transformer.setOutputProperty("indent", "yes"); DOMSource source = new DOMSource(); // 将待转换输出节点赋值给DOM源模型的持有者(holder) source.setNode(node); StreamResult result = new StreamResult(); if (filename == null) { // 设置标准输出流为transformer的底层输出目标 result.setOutputStream(System.out); }//if else { result.setOutputStream(new FileOutputStream(filename)); }//else // 执行转换从源模型到控制台输出流 transformer.transform(source, result); } catch (TransformerConfigurationException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args){ try { // 创建一个DocumentBuilderFactory对象 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 创建DocumentBuilder对象 DocumentBuilder builder = factory.newDocumentBuilder(); // 解析XML文件获取Document对象(注意:org.w3c.dom.Document) Document document = builder.parse("D:\\bookstore.xml"); // 获取根节点 Element rootNode = document.getDocumentElement(); // 1. 添加节点book Element bookNode = document.createElement("book"); // 添加属性 bookNode.setAttribute("category", "Java"); // 添加节点title Element titleNode = document.createElement("title"); // 添加属性(第二种方法:先创建一个属性节点,设置名称和值) Attr langAttr = document.createAttribute("lang"); langAttr.setNodeValue("chi"); titleNode.setAttributeNode(langAttr); // 设置文本内容 titleNode.setTextContent("Java并发编程实战"); // 设置为bookNode的子节点 bookNode.appendChild(titleNode); // 添加节点author Element authorNode = document.createElement("author"); // 设置文本内容 authorNode.setTextContent("Brian Goetz"); // 设置为bookNode的子节点 bookNode.appendChild(authorNode); // 添加节点price Element priceNode = document.createElement("price"); // 设置文本内容 priceNode.setTextContent("69.00"); // 设置为bookNode的子节点 bookNode.appendChild(priceNode); // bookNode节点插入节点树中 rootNode.appendChild(bookNode); outputToXml(rootNode,"D:\\bookstore.xml"); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ 运行结果: ~~~ <?xml version="1.0" encoding="utf-8"?><bookstore> <book category="Java"> <title lang="chi">Java多线程编程核心技术</title> <author>高洪岩</author> <year>2015</year> <price>69.00</price> </book> <book category="C++"> <title lang="en">Effective C++: 55 Specific Ways to Improve Your Programs and Designs</title> <author>Scott Meyers</author> <year>2006</year> <price>58.00</price> </book> <book category="Web"> <title lang="en">Learning XML</title> <author>Erik T. Ray</author> <year>2003</year> <price>39.95</price> </book> <book category="Java"> <title lang="chi">Java并发编程实战</title> <author>Brian Goetz</author> <price>69.00</price> </book> </bookstore> ~~~ 我们可以看到添加了一个book元素节点。 ### 5.查找修改节点 ~~~ package com.qunar.xml; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; public class DomXmlCode { public static void outputToXml(Node node, String filename) { try { TransformerFactory transFactory = TransformerFactory.newInstance(); Transformer transformer = transFactory.newTransformer(); // 设置各种输出属性 transformer.setOutputProperty("encoding", "utf-8"); transformer.setOutputProperty("indent", "yes"); DOMSource source = new DOMSource(); // 将待转换输出节点赋值给DOM源模型的持有者(holder) source.setNode(node); StreamResult result = new StreamResult(); if (filename == null) { // 设置标准输出流为transformer的底层输出目标 result.setOutputStream(System.out); }//if else { result.setOutputStream(new FileOutputStream(filename)); }//else // 执行转换从源模型到控制台输出流 transformer.transform(source, result); } catch (TransformerConfigurationException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args){ try { // 创建一个DocumentBuilderFactory对象 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 创建DocumentBuilder对象 DocumentBuilder builder = factory.newDocumentBuilder(); // 解析XML文件获取Document对象(注意:org.w3c.dom.Document) Document document = builder.parse("D:\\bookstore.xml"); // 获取根节点 Element rootNode = document.getDocumentElement(); // book集合 NodeList bookList = document.getElementsByTagName("book"); // 第四本书 Node bookNode = bookList.item(3); // 修改属性 NamedNodeMap attrMap = bookNode.getAttributes(); Node attrNode = attrMap.item(0); attrNode.setNodeValue("Hadoop"); // title子节点集合 NodeList titleList = document.getElementsByTagName("title"); // title子节点 Node titleNode = titleList.item(3); titleNode.setTextContent("Hadoop权威指南"); // author子节点集合 NodeList authorList = document.getElementsByTagName("author"); // author子节点 Node authorNode = authorList.item(3); authorNode.setTextContent("周敏奇"); // price子节点集合 NodeList priceList = document.getElementsByTagName("price"); // price子节点 Node priceNode = priceList.item(3); priceNode.setTextContent("79"); outputToXml(rootNode,"D:\\bookstore.xml"); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ **运行结果:** ~~~ <?xml version="1.0" encoding="utf-8"?><bookstore> <book category="Java"> <title lang="chi">Java多线程编程核心技术</title> <author>高洪岩</author> <year>2015</year> <price>69.00</price> </book> <book category="C++"> <title lang="en">Effective C++: 55 Specific Ways to Improve Your Programs and Designs</title> <author>Scott Meyers</author> <year>2006</year> <price>58.00</price> </book> <book category="Web"> <title lang="en">Learning XML</title> <author>Erik T. Ray</author> <year>2003</year> <price>39.95</price> </book> <book category="Hadoop"> <title lang="chi">Hadoop权威指南</title> <author>周敏奇</author> <price>79</price> </book> </bookstore> ~~~ ### 6.删除节点 ~~~ package com.qunar.xml; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; public class DomXmlCode { public static void outputToXml(Node node, String filename) { try { TransformerFactory transFactory = TransformerFactory.newInstance(); Transformer transformer = transFactory.newTransformer(); // 设置各种输出属性 transformer.setOutputProperty("encoding", "utf-8"); transformer.setOutputProperty("indent", "yes"); DOMSource source = new DOMSource(); // 将待转换输出节点赋值给DOM源模型的持有者(holder) source.setNode(node); StreamResult result = new StreamResult(); if (filename == null) { // 设置标准输出流为transformer的底层输出目标 result.setOutputStream(System.out); }//if else { result.setOutputStream(new FileOutputStream(filename)); }//else // 执行转换从源模型到控制台输出流 transformer.transform(source, result); } catch (TransformerConfigurationException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args){ try { // 创建一个DocumentBuilderFactory对象 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 创建DocumentBuilder对象 DocumentBuilder builder = factory.newDocumentBuilder(); // 解析XML文件获取Document对象(注意:org.w3c.dom.Document) Document document = builder.parse("D:\\bookstore.xml"); // 获取根节点 Element rootNode = document.getDocumentElement(); // book集合 NodeList bookList = document.getElementsByTagName("book"); // 第三本书 Node book3Node = bookList.item(2); // year节点集合 NodeList yearList = document.getElementsByTagName("year"); // 删除第三本书的第三个子节点(year) book3Node.removeChild(yearList.item(2)); // 第四本书 Node book4Node = bookList.item(3); // 删除第四本书 rootNode.removeChild(book4Node); outputToXml(rootNode,"D:\\bookstore.xml"); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ **运行结果:** ~~~ <?xml version="1.0" encoding="utf-8"?><bookstore> <book category="Java"> <title lang="chi">Java多线程编程核心技术</title> <author>高洪岩</author> <year>2015</year> <price>69.00</price> </book> <book category="C++"> <title lang="en">Effective C++: 55 Specific Ways to Improve Your Programs and Designs</title> <author>Scott Meyers</author> <year>2006</year> <price>58.00</price> </book> <book category="Web"> <title lang="en">Learning XML</title> <author>Erik T. Ray</author> <price>39.95</price> </book> </bookstore> ~~~
';

[Java开发之路](9)对象序列化与反序列化

最后更新于:2022-04-01 09:59:44

### 1. 对象序列化 当你创建对象时,只要你需要,它会一直存在,但是程序终止时,无论何时它都不会继续存在。尽管这样做是非常有意义的,但是在某些情况下,如果程序不运行时扔能存在并且保存其信息,那将对我们非常有用。这样,在下次程序运行时,该对象将被重建并且拥有的信息与程序上次运行时它所拥有的信息相同。当然,我们也可以通过将信息写入文件或者数据库,但是如果能将一个对象声明为是"持久性"的,并为我们处理掉所有的细节,这将会显得十分方便。 Java的序列化是将那些实现了Serializable接口的对象转换为一个字节序列,并能够在以后需要的时候将这个字节序列完全恢复为原来的对象。我们可以在windows系统机器上创建一个对象,将其序列化,通过网络将它发送给一台运行Unix系统的计算机,然后在那里准确的重新组装恢复为原来的对象,所以说不必担心数据在不同机器上的表示会不同,也不必关心字节的顺序或者其他任何细节。这意味着序列化机制能自动弥补不同操作系统之间的差异。 对象序列化是为了支持两种特性。一是Java的远程方法调用(RMI),它使存活于其他计算机上的对象使用起来就像存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。二是Java Beans,使用一个bean时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复(这种具体工作就是由对象序列化完成的)。 要序列化一个对象,首先要创建一个OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需调用writeObject()即可将对象序列化,并将其发送给 OutputStream(对象序列化是基于字节的,因要使用InputStream和OutputStream继承层次结构)。要反向进行该过程(将一个序列化还原为一个对象),需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们。 ~~~ package com.qunar.bean; import java.io.Serializable; /** * 学生实体类 * @author sjf0115 * */ public class Student implements Serializable{ /** * */ private static final long serialVersionUID = 1L; // 姓名 private String name; // 学号 private String ID; // 年龄 private int age; // 学校 private String school; /** * @param name * @param iD * @param age * @param school */ public Student(String name, String id, int age, String school) { super(); this.name = name; ID = id; this.age = age; this.school = school; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getID() { return ID; } public void setID(String iD) { ID = iD; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSchool() { return school; } public void setSchool(String school) { this.school = school; } @Override public String toString() { return "姓名:" + name + " 学号:" + ID + " 年龄:" + age + " 学校:" + school; } } ~~~ ~~~ package com.qunar.io; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import com.qunar.bean.Student; public class SeriaCode { public static void main(String[] args) { // 对象序列化数据保存位置 String path = "D:\\seria.dat"; try { // 创建FileOutputStream对象 FileOutputStream fileOutputStream = new FileOutputStream(path); // 创建ObjectOutputStream对象 ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); // 序列化对象 Student student = new Student("xiaosi","130346",25,"西安电子科技大学"); // 进行对象序列化 Student对象要实现序列化接口 objectOutputStream.writeObject(student); objectOutputStream.flush(); objectOutputStream.close(); // 创建FileInputStream对象 FileInputStream fileInputStream = new FileInputStream(path); // 创建ObjectInputStream对象 ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); // 反序列化为对象 Student stu = (Student)objectInputStream.readObject(); objectInputStream.close(); System.out.println("Stu->"+stu); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ~~~ ### 2.寻找类 将一个对象从它的序列化状态中恢复出来,有哪些工作是必须的?举例来说,假如我们将一个对象序列化,并通过网络将其作为文件传送给另一台计算机,那么另一台计算机上的程序可以只利用该文件内容来还原这个对象吗? ~~~ package com.qunar.io.serial; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; public class SerialCode2 { public static void main(String[] args) { try { // 创建FileInputStream对象 FileInputStream fileInputStream = new FileInputStream("seria.dat"); // 创建ObjectInputStream对象 ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); // 反序列化为对象 Object object = objectInputStream.readObject(); objectInputStream.close(); System.out.println(object.getClass()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ~~~ 假设在运行上面程序之前,将Student类删除,在运行,会得到: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:rgb(0,0,128); font-family:微软雅黑"><u>java.lang.ClassNotFoundException<span style="text-decoration:none; color:rgb(255,0,0)">: com.qunar.io.Student</span></u></span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.net.URLClassLoader$1.run(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.net.URLClassLoader$1.run(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.security.AccessController.doPrivileged(<span style="color:rgb(0,0,128)"><u>Native Method</u></span>)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.net.URLClassLoader.findClass(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.lang.ClassLoader.loadClass(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.lang.ClassLoader.loadClass(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.lang.Class.forName0(<span style="color:rgb(0,0,128)"><u>Native Method</u></span>)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.lang.Class.forName(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectInputStream.resolveClass(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectInputStream.readClassDesc(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectInputStream.readObject0(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectInputStream.readObject(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.qunar.io.serial.SerialCode2.main(<span style="color:rgb(0,0,128)"><u>SerialCode2.java:17</u></span>)</span></div></td></tr></tbody></table> 打开文件和读取Student对象中内容都需要Student的class对象,而虚拟机找不到Student.calss,这样会导致抛出ClassNotFoundException异常。所以必须保证虚拟机能够找到相关的.class文件。 从这里就可以证明得到:不能只利用序列化字节数据文件来得到原先对象,还必须对应类的.class文件。 ### 3.序列化控制 默认的序列化机制并不难控制。然而,如果有特殊的需要那又该怎么办?例如,也许考虑特殊的安全问题,而且你不希望对象的某一部分被序列化;或者一个对象还原以后,某子对象需要重新创建,从而不必将该子对象序列化。 为了应对这些特殊的情况,可通过Externalizable接口(代替实现Serializable接口)来对序列化过程进行控制。这个Externalizable接口继承了Serializable接口,同时还增添了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原过程中自动调用,以便执行一些特殊操作。 ~~~ package com.qunar.io; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class Fruit implements Externalizable{ public Fruit(){ System.out.println("Fruit constructor..."); } @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println("Fruit writeExternal..."); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("Fruit readExternal..."); } } ~~~ ~~~ package com.qunar.io; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class Fruit2 implements Externalizable{ Fruit2(){ System.out.println("Fruit2 constuctor..."); } @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println("Fruit writeExternal..."); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("Fruit readExternal..."); } } ~~~ ~~~ package com.qunar.io; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class FruitSerialCode { public static void main(String[] args) { try { // 创建FileOutputStream对象 FileOutputStream fileOutputStream = new FileOutputStream("fruit.out"); // 创建ObjectOutputStream对象 ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); // 序列化对象 Fruit fruit = new Fruit(); Fruit2 fruit2 = new Fruit2(); // 进行对象序列化 Fruit对象要实现序列化接口 System.out.println("writeObject..."); objectOutputStream.writeObject(fruit); objectOutputStream.writeObject(fruit2); objectOutputStream.flush(); objectOutputStream.close(); // 创建FileInputStream对象 FileInputStream fileInputStream = new FileInputStream("fruit.out"); // 创建ObjectInputStream对象 ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); // 反序列化为对象 System.out.println("readFruit..."); fruit = (Fruit)objectInputStream.readObject(); System.out.println("readFruit2..."); fruit2 = (Fruit2)objectInputStream.readObject(); objectInputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit2 constuctor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit writeExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit writeExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit readExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">readFruit2...</span></div><div><span style="font-size:14pt; color:rgb(0,0,128); font-family:微软雅黑"><u>java.io.InvalidClassException<span style="text-decoration:none; color:rgb(255,0,0)">: com.qunar.io.Fruit2; no valid constructor</span></u></span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectInputStream.readObject0(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at java.io.ObjectInputStream.readObject(Unknown Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微软雅黑">    at com.qunar.io.FruitSerialCode.main(<span style="color:rgb(0,0,128)"><u>FruitSerialCode.java:36</u></span>)</span></div></td></tr></tbody></table> Fruit和Fruit2除了细微的差别之外,几乎完全一致。上例中没有反序列化后Fruit2对象,并且导致了一个异常。主要是Fruit的构造函数是public的,而Fruit2的构造函数却不是,这样就会在反序列时抛出异常。 反序列化fruit后,会调用Fruit的默认构造函数。这与反序列一个Serializable对象不同。对于一个Serializable对象,对象完全以它存储的二进制位为基础,而不用调用构造函数。而对于一个Externalizable对象,所有普通的构造函数都会被调用(包括在字段定义时的初始化),然后调用readExternal()。 注意: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/>      <span style="color:#ff6820">所有默认的构造函数都会被调用,才能使Externalizable对象产生正确的行为。</span><br/></td></tr></tbody></table> 下面例子示范如何正确的序列化和反序列一个Externalizable对象: ~~~ package com.qunar.io; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class Fruit implements Externalizable{ private String name; private int num; // 必须有默认构造函数 反序列时使用 public Fruit(){ System.out.println("Fruit default constructor..."); } /** * @param name * @param num */ public Fruit(String name, int num) { System.out.println("Fruit constructor..."); this.name = name; this.num = num; } @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println("Fruit writeExternal..."); // 必须做如下操作 out.writeObject(name); out.writeInt(num); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("Fruit readExternal..."); // 必须做如下操作 name = (String)in.readObject(); num = in.readInt(); } @Override public String toString() { return "name:" + name + " num:" + num; } } ~~~ ~~~ package com.qunar.io; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class FruitSerialCode { public static void main(String[] args) { try { // 创建FileOutputStream对象 FileOutputStream fileOutputStream = new FileOutputStream("fruit.out"); // 创建ObjectOutputStream对象 ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); // 序列化对象 Fruit fruit = new Fruit("苹果",20); // 进行对象序列化 Fruit对象要实现序列化接口 System.out.println("writeObject..."); objectOutputStream.writeObject(fruit); objectOutputStream.flush(); objectOutputStream.close(); // 创建FileInputStream对象 FileInputStream fileInputStream = new FileInputStream("fruit.out"); // 创建ObjectInputStream对象 ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); // 反序列化为对象 System.out.println("readFruit..."); fruit = (Fruit)objectInputStream.readObject(); System.out.println("Fruit->[" + fruit + "]"); objectInputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit writeExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit default constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit readExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit-&gt;[name:苹果  num:20]</span></div></td></tr></tbody></table> 可以看出,name和num只在第二个构造函数中初始化,而不是在默认的构造函数中初始化。所以说,假如不在readExternal初始化name和num,name就会为null,age就会为0,如果注释掉代码中"必须做如下操作"之后的代码,反序列化之后的对象的name为null,num为0。 <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit writeExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit default constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit readExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Fruit-&gt;[name:null  num:0]</span></div></td></tr></tbody></table> 如果我们从一个Externalizable对象继承,通常需要调用基类版本的writeExternal()和readExternal()来为基类组件提供恰当的序列化和反序列化功能。因此,为了正常运行,我们不仅需要在writeExternal()方法(没有任何默认行为来为Externalizable对象写入任何成员对象)中将来自对象的重要信息写入,还必须在readExternal()方法中恢复数据。 ### 4.transient关键字 但我们对序列化进行控制时,可能某个特定属性不想让Java序列化机制自动保存与恢复。如果属性表示的是我们不希望将其序列化的敏感信息(如密码),就会遇到这种问题。即使对象中的这些信息是private属性,一经序列化处理,人们就可以通过读取文件或者拦截网络传输的方式访问到它。 (1)防止对象敏感信息被序列化,可以将类实现为Externalizable,像前面一样。这样就没有任何东西可以自动序列化,并且可以在writeExternal()内部只对所需部分进行显示的序列化。 (2)如果我们操作的是Serializable对象,那么所有序列化操作都会自动进行。为了进行控制,使用transient关键字关闭序列化操作,它的意思"不用麻烦你序列化或者反序列化数据,我自己会处理的"。 假设我们用Login类保存某个特定的登录会话信息。登录的合法性得到检验之后,我们想把数据保存下来,但不包括密码。 ~~~ package com.qunar.io; import java.io.Serializable; import java.util.Date; public class Login implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private Date date = new Date(); private String userName; // 防止被序列化transient private transient String password; /** * @param date * @param userName * @param password */ public Login(String userName, String password) { super(); this.userName = userName; this.password = password; } @Override public String toString() { return "Date:" + date + " UserName:" + userName + " Password:" + password; } } package com.qunar.io; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class LoginSerialCode { public static void main(String[] args) { try { // 创建FileOutputStream对象 FileOutputStream fileOutputStream = new FileOutputStream("login.out"); // 创建ObjectOutputStream对象 ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); // 序列化对象 Login login = new Login("xiaosi", "123"); // 进行对象序列化 Fruit对象要实现序列化接口 System.out.println("writeObject..."); objectOutputStream.writeObject(login); objectOutputStream.flush(); objectOutputStream.close(); // 创建FileInputStream对象 FileInputStream fileInputStream = new FileInputStream("login.out"); // 创建ObjectInputStream对象 ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); // 反序列化为对象 System.out.println("readFruit..."); login = (Login)objectInputStream.readObject(); System.out.println("LoginInfo->[" + login + "]"); objectInputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">LoginInfo-&gt;[Date:Thu Dec 31 00:16:13 CST 2015  UserName:xiaosi Password:</span><span style="font-size:14pt; font-family:微软雅黑"><span style="color:#ff6820">null</span></span><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">]</span></div></td></tr></tbody></table> date和useName(不是transient),所以它们会自动被序列化,而password是transient的,所以不会被自动保存到磁盘;另外自动序列化机制也不会去恢复它,当对象恢复时,password就会变成null。同时我们发现date字段被存储在磁盘并且从磁盘上恢复出来,而不是重新生成。 (3)Externalizable的替代方法 如果你不使用Externalizable,我们还有一种方法。我们可以实现Serializable接口,并添加writeObject()和readObject()方法。这样一旦进行序列化和反序列化,就会自动的分别调用这两个方法,来代替默认的序列化机制。 ~~~ package com.qunar.io; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Date; public class Login implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private Date date = new Date(); private String userName; // 防止被序列化transient private transient String password; /** * @param date * @param userName * @param password */ public Login(String userName, String password) { super(); this.userName = userName; this.password = password; } // 必须有 private void writeObject(ObjectOutputStream stream) throws IOException{ // 默认的序列化 stream.defaultWriteObject(); // 手动完成序列化 stream.writeObject(password); } // 必须有 private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException{ // 默认的反序列化 stream.defaultReadObject(); // 手动完成反序列化 password = (String)stream.readObject(); } @Override public String toString() { return "Date:" + date + " UserName:" + userName + " Password:" + password; } } ~~~ ~~~ package com.qunar.io; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class LoginSerialCode { public static void main(String[] args) { try { // 创建FileOutputStream对象 FileOutputStream fileOutputStream = new FileOutputStream("login.out"); // 创建ObjectOutputStream对象 ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); // 序列化对象 Login login = new Login("xiaosi", "123"); // 进行对象序列化 Fruit对象要实现序列化接口 System.out.println("writeObject..."); objectOutputStream.writeObject(login); objectOutputStream.flush(); objectOutputStream.close(); // 创建FileInputStream对象 FileInputStream fileInputStream = new FileInputStream("login.out"); // 创建ObjectInputStream对象 ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); // 反序列化为对象 System.out.println("readFruit..."); login = (Login)objectInputStream.readObject(); System.out.println("LoginInfo->[" + login + "]"); objectInputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">LoginInfo-&gt;[Date:Fri Jan 01 15:57:53 CST 2016  UserName:xiaosi </span><span style="font-size:14pt; font-family:微软雅黑"><span style="color:#ff6820">Password:123</span></span><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">]</span></div></td></tr></tbody></table> 相比上面实验,password恢复出来了。在这个例子中,password字段是transient字段,用来证明非transient字段是由defaultWriteObject()方法保存,而transient字段是必须在程序中明确保存和恢复。 ### 5.使用"持久化" 一个诱人的使用序列化技术的想法:存储程序的一些状态,以便我们随后可以很容易的将程序恢复到当前的状态。但是在我们能够这样做之前,必须回答几个问题。如果我们将两个对象(都具有指向第三个对象的引用)进行序列化,会发生什么状况?当我们从它们的序列化状态恢复这两个对象时,第三个对象会只出现一次吗? ~~~ package com.qunar.io; import java.io.Serializable; public class House implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private String name; private String location; /** * @param name * @param location */ public House(String name, String location) { super(); this.name = name; this.location = location; ~~~ ~~~ package com.qunar.io; import java.io.Serializable; public class Animal implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private String name; private House house; /** * 构造函数 * @param name * @param house */ public Animal(String name, House house) { super(); this.name = name; this.house = house; } @Override public String toString() { return name + " " + house; } } ~~~ ~~~ package com.qunar.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; public class AnimalSerialCode { @SuppressWarnings("unchecked") public static void main(String[] args) { House house = new House("水立方","北京海淀区"); List<Animal> animals = new ArrayList<Animal>(); animals.add(new Animal("狗", house)); animals.add(new Animal("鸡", house)); animals.add(new Animal("羊", house)); System.out.println("Animals->" + animals); try { ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(buf); // 序列化 objectOutputStream.writeObject(animals); objectOutputStream.writeObject(animals); ByteArrayOutputStream buf2 = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(buf2); // 序列化 objectOutputStream2.writeObject(animals); ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray())); List<Animal> ani1 = (List<Animal>)objectInputStream.readObject(); List<Animal> ani2 = (List<Animal>)objectInputStream.readObject(); ObjectInputStream objectInputStream2 = new ObjectInputStream(new ByteArrayInputStream(buf2.toByteArray())); List<Animal> ani3 = (List<Animal>)objectInputStream2.readObject(); System.out.println("Animals1->"+ani1); System.out.println("Animals2->"+ani2); System.out.println("Animals3->"+ani3); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Animals-&gt;[狗   com.qunar.io.House@1b15692, 鸡   com.qunar.io.House@1b15692, 羊   com.qunar.io.House@1b15692]</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Animals1-&gt;[狗   com.qunar.io.House@1aaf0b3, 鸡   com.qunar.io.House@1aaf0b3, 羊   com.qunar.io.House@1aaf0b3]</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Animals2-&gt;[狗   com.qunar.io.House@1aaf0b3, 鸡   com.qunar.io.House@1aaf0b3, 羊   com.qunar.io.House@1aaf0b3]</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Animals3-&gt;[狗   com.qunar.io.House@1a082e2, 鸡   com.qunar.io.House@1a082e2, 羊   com.qunar.io.House@1a082e2]</span></div></td></tr></tbody></table> 我们可以通过字节数组来使用对象序列化,从而实现任何可Serializable对象的"深度复制"(意味着复制的是整个对象网,而不仅仅是基本对象及其引用)。在这个例子中,Animal对象包含House类型字段。我们创建Animals列表并将其两次序列化,分别送至不同的流。当期被反序列化还原被打印时,我们可以看到:每次运行时对象将会处在不同的内存地址。 当然我们期望这些反序列还原后的对象地址与原来的对象地址不同,但是Animals1和Animals2却出现了相同的地址。当恢复Animals3时,系统无法知道另一个流内的对象是第一个流内对象额别名,因此会产生完全不同的对象网。 只要将任何对象序列化到单一流中,就可以会付出与我们写出时一样的对象网,并且没有任何意外重复复制的对象。如果想保存系统状态,最安全的做法就是将其作为"原子"操作进行序列化。
';

[Java开发之路](8)输入流和输出流

最后更新于:2022-04-01 09:59:41

### 1. Java流的分类 按流向分: 输入流: 可以从其中读入一个字节序列的对象称作输入流。 输出流: 可以向其中写入一个字节序列的对象称作输出流。 这些字节序列的来源地和目的地可以是文件,而且通常都是文件,但是也可以是网络连接,甚至是内存块。抽象类InputStream和OutputStream构成了输入和输出类层结构的基础。 按数据传输单位分: 字节流: 以字节为单位传输数据的流 字符流: 以字符为单位传输数据的流 按功能分: 节点流: 用于直接操作目标设备的流 过滤流: 是对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能。 ### 2.读写字节(InputStream和OutputStream) (1)InputStream类有一个抽象方法: ~~~ abstract int read() ~~~ 这个方法将读入一个字节,并返回读入的字节,或者在遇到输入源结尾时返回-1。在设计具体的输入流时,必须覆盖这个方法以提供适用的功能,例如,在FileInputStream类中,这个方法将从某个文件中读入一个字节。 InputStream类还有若干个非抽象的方法,它们可以读入一个字节数组,或者跳过大量的字节。这些方法都要调用抽象的read方法,因此各个子类都只需覆盖一个方法。 (2)OutputStream类定义了下面的抽象方法: ~~~ abstract void write(int b) ~~~ 它可以向某个输出位置写出一个字节。 (3)read和write方法在执行时都将阻塞,直至字节确实被读入或写出。这就意味着如果流不能被立即访问(通常因为网络连接忙),那么当前的线程将被阻塞。这使得在这两个方法等待指定流变为可用的这段时间内,其他的线程就有机会去执行有用的工作。 当你完成对流的读写时,应该通过调用close方法来关闭它,这个调用会释放掉十分有限的操作系统资源。如果一个应用程序打开了过多的流而没有关闭,那么系统资源将被耗尽。关闭一个输出流的同时还会冲刷用于该输出流的缓冲区:所有被临时置于缓冲区中,以便用更大的包的形式传递的字符在关闭输出流时都将被送出。如果不关闭文件,那么写出的字节的最后一个包可能将永远也得不到传递。我们可以使用flush方法认为的冲刷这些输出。 即使某个流类提供了使用原生的read和write功能的某些具体方法,应用系统的程序员还是很少使用它们,因为大家感兴趣的数据可能包含数字,字符串和对象,而不是原生字节。 Java提供了众多从基本InputStream和OutputStream类导出的类,这些类使我们可以处理那些以常用格式表示的数据,而不只是字节。 ### 3.流家族 流家族中的成员按照它们的使用方法进行划分,形成了处理字节和字符的两个单独的层次结构。Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。因此JAVA才引入字符流。 java.io包中包含了流式I/O所需要的所有类。在java.io包中有四个基本类:InputStream、OutputStream及Reader、Writer类,它们分别处理字节流和字符流。 | **输入/输出** | **字节流** | **字符流** | |-----|-----|-----| | 输入流 | InputStream | Reader | | 输出流 | OutputStream | Writer | 字节方面:InputStream和OutputStream类读写单子个字节或字节数组。要想读写字符串和数字,就需要功能更强大的子类,例如,DataInputStream和DataOutputStream可以以二进制格式读写所有的基本Java类型。 字符方面:对于Unicode文本,可以使用抽象类Reader和Writer的子类。Reader和Writer类的基本方法与InputStream和OutputStream中的方法类似。 ### 4.字节流InputStream和OutputStream ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a7be296.jpg) 4.1 InputStream抽象类 InputStream 为字节输入流,它本身为一个抽象类,必须依靠其子类实现各种功能,此抽象类是表示字节输入流的所有类的超类。 继承自InputStream 的流都是向程序中输入数据的,且数据单位为字节(8bit); InputStream是输入字节数据用的类,所以InputStream类提供了3种重载的read方法。Inputstream类中的常用方法:  | **常用方法** | **描述** | |-----|-----| | public abstract int read( ) | 从输入流中读取下一个字节数据。返回字节使用高位补0的int类型值表示(0-255),若返回值为-1说明没有读取到任何字节,输入流达到尽头。 | | public int read(byte b[ ]) | 从输入流中读取b.length个字节的数据放到字节数组b中。返回值是读取的字节数。如果字节数组的长度为0,不会读取任何字节数据,返回0,否则至少尝试去读取一个字节的数据。如果没有获取到字节数据,表示流到达文件末尾,返回-1。第一个读取的字节存储在b[0],以此类推。 | | public int read(byte b[ ], int off, int len) | 从输入流中读取至多len个字节的数据放到字节数组b中。返回值是读取的实际字节数。如果字节数组的长度为0,不会读取任何字节数据,返回0,否则至少尝试去读取一个字节的数据。如果没有获取到字节数据,表示流到达文件末尾,返回-1。第一个读取的字节存储在b[off],下一个存储在b[off+1],以此类推。 | | public int available( ) | 返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用。注意:虽然很多InputStream的实现类可以正确的返回输入流的总字节数,但是并不是全都都可以。所以使用这个方法的返回值去分配字节大小来容纳输入流的所有数据一定不是一个正确的方法。 | | public long skip(long n) | 忽略输入流中的n个字节,返回值是实际忽略的字节数, 如果为负数,表示没有跳过任何字节数据。 | | public int close( ) | 关闭输入流,释放分配给输入流的系统资源。 | InputStream子类: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a7d9159.jpg) InputStream的作用是用来表示那些那些从不同数据源产生输入的类。这些数据源包括: - 字节数组 - String对象 - 文件 - "管道",工作方式与实际管道相似,从一端进入,从一端输出 - 一个由其他种类的流组成的序列,以便我们可以将它们收集合并到一个流内 每一种数据源都有相应的InputStream子类。另外,FilterInputStream也属于一种InputStream,为"装饰器"类提供基类,其中"装饰器"类可以把属性或有用的接口与输入流连接在一起。 | **类** | **功能** | |-----|-----| | ByteArrayInputStream | 允许将内存中缓冲区当做InputStream使用 | | StringBufferInputStream | 将String转换成InputStream | | FileInputStream | 用于从文件中读取信息 | | PipedInputStream | 产生用于写入相关PipedOutputStream的数据。实现“管道化”概念。 | | SequenceInputStream | 将两个或者多个InputStream对象转换成单一InputStream | | FilterInputStream | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其他的InputStream类提供有用的功能。 | 4.2 OutputSream抽象类 OutputStream提供了3个write方法来做数据的输出,这个是和InputStream是相对应的。 | **常用方法** | **描述** | |-----|-----| | public abstract void write(int b) | 将指定字节写入到输出流中。一般是将参数b的低八位(一个字节)写入到输出流中。b的高八位被忽略掉。 | | public void write(byte[] b) | 从字节数组b中向输出流中写入b.length个字节数据。 | | public void write(byte[] b,int off,int len) | 从字节数组b偏移位置为off的开始位置向输出流写入len个字节数据。b[off]是第一个被写入的字节,b[off+len-1]是最后一个被写入的字节。如果b为null,会抛出NullPointer异常;如果off或者len是负数,或者off+len比字节数组b的长度大,则会抛出IndexOutOfBoundsException异常。 | | public void flush() | 清空输出流,并强制将所有缓冲的输出字节被写出。 | | public void close() | 关闭输出流,释放分配给输出流的系统资源。 | OutputStream的子类: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a7ecb6b.jpg) 该类别的类决定了输出所要去往的目标:字节数组(但不是String,不过你当然可以使用字节数组自己来创建),文件或管道。 | **类** | **功能** | |-----|-----| | ByteArrayOutputStream | 在内存中创建缓冲区,所有送往“流”的数据都要放置在此缓冲区。 | | FileOutputStream | 用于将信息写至文件。 | | PipedOutputStream | 任何写入其中的信息都会自动作为PipedInputStream的输出。实现“管道化”概念。 | | FilterOutputStream | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其他的OutputStream类提供有用的功能。 | ### 5.字符流 Reader和Writer 当我们初次看到Reader和Writer类时,可能会以为这是两个用来代替InputStream和OutputStream的类,但实际上不是。尽管一些原始的“流”类库不再使用(如果使用它们,则会收到编译器的警告信息),但是InputStream和OutputStream在以面向字节流形式的IO时仍然可以提供有价值的功能。Reader和Writer则提供了兼容Unicode与面向字符的I/O功能。有时候我们还会把来自"字节"层次结构中的类和来自"字符"层次结构中类结合使用。为了实现这个目标,我们要用到"适配器"(adapter)类:InputStreamReader可以把InputStream转换为Reader,而OutputStream可以把OutputStream转换为Writer。 设计Reader和Writer继承层次结构是为了国际化。老的IO流继承层次结构只能支持8位字节流,并且不能很好的处理16位的Unicode字符。设计它的目的就是为了在所有的IO操作中都支持Unicode。 Reader的子类: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a80bd99.jpg) Writer的子类: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a81f6bf.jpg) ### 6.FileInputStream和FileOutputStream 6.1 FileInputStream FileInputStream 从文件系统中的某个文件中获得输入字节。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a835938.jpg) (1)构造方法 | **构造方法** | **描述** | |-----|-----| | FileInputStream(String name) | 使用由name字符串指定路径名文件创建FileInputStream对象 | | FileInputStream(File file) | 使用由file对象指定路径名的文件创建FileInputStream对象 | ~~~ // FileInputStream(String name) String path = "D:\\Recommended system.txt"; FileInputStream stream = new FileInputStream(path); // FileInputStream(File file) File file = new File(path); FileInputStream stream2 = new FileInputStream(file); ~~~ (2)说明 - 用于读取诸如图像数据之类的原始字节流。(要读取字符流,请考虑使用 FileReader) - 包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。 - 类本身只是简单地重写那些将所有请求传递给所包含输入流的 InputStream 的所有方法。 - 其子类可进一步重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段。 (3)实例 ~~~ package com.qunar.io; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class IOUtil { public static void main(String[] args) { try { String path = "D:\\demo.txt"; FileInputStream stream = new FileInputStream(path); int num = 100; byte[] buff = new byte[num]; while((stream.read(buff,0,num)) != -1){ System.out.println("NewLine->"+new String(buff)); }//while stream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">NewLine-&gt;My father was a self-taught mandolin player. He was one of the best string instrument players in our</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">NewLine-&gt; town. He could not read music, but if he heard a tune a few times, he could play it. When he was yo</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">NewLine-&gt;unger, he was a member of a small country music band. They would play at local dances and on a</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">NewLine-&gt;ccasions would play for the local radio station. He often told us how he had auditioned and earned a</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">NewLine-&gt; position in a band that featured Patsy Cline as their lead singer. He told the family that after he</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">NewLine-&gt; was hired he never went back. Dad was a very religious man. He stated that there was a lot of drink</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">NewLine-&gt;ing and cursing the day of his audition and he did not want to be around that type of environment.nk</span></div></td></tr></tbody></table> 6.2 FileOutputStream ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a848610.jpg) (1)构造函数 | **构造函数** | **描述** | |-----|-----| | FileOutputStream(String name) | 使用由name字符串指定路径名的文件创建一个新的文件输出流。 | | FileOutputStream(String name,boolean append) | 使用由name字符串指定路径名的文件创建一个新的文件输出流。如果append参数为true,那么数据将被添加到文件末尾,而具有相同名字的已有文件不会被删除(末尾添加数据);否则这个方法删除所有具有相同名字的已有文件。 | | FileOutputStream(File file) | 使用由file对象指定路径名的文件创建一个新的文件输出流。 | | FileOutputStream(File file,boolean append) | 使用由file对象指定路径名的文件创建一个新的文件输出流。如果append参数为true,那么数据将被添加到文件末尾,而具有相同名字的已有文件不会被删除(末尾添加数据);否则这个方法删除所有具有相同名字的已有文件。 | (2)案例 ~~~ package com.qunar.io; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class IOUtil { public static void main(String[] args) { try { String path = "D:\\from.txt"; String path2 = "D:\\to.txt"; FileInputStream inputStream = new FileInputStream(path); FileOutputStream outputStream = new FileOutputStream(path2); int num = 100; byte[] buff = new byte[num]; // 由文件写至内存 while((inputStream.read(buff,0,num)) != -1){ // 由内存写至文件中 outputStream.write(buff); }//while inputStream.close(); outputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ ### 7.DataInputStream和DataOutputStream DataInputStream和DataOutputStream是对流的扩展,让我们操作Java基本数据类型更加简单。 数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型。 ~~~ package com.qunar.io; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class IOUtil { public static void main(String[] args) { try { String path = "D:\\to.txt"; // 向文件写入操作 FileOutputStream outputStream = new FileOutputStream(path); DataOutputStream dataOutputStream = new DataOutputStream(outputStream); // 向文件中写入一个Int值 dataOutputStream.writeInt(10); // 向文件中写入一个Double值 dataOutputStream.writeDouble(0.98); // 向文件中写入一个Long值 dataOutputStream.writeLong(12l); // 向文件中写入一个UTF-8编码值 dataOutputStream.writeUTF("我来自西安电子科技大学"); //从文件读取操作 FileInputStream inputStream = new FileInputStream(path); DataInputStream dataInputStream = new DataInputStream(inputStream); // 从文件中读取一个Int值 System.out.println("从文件中读取一个Int值:" + dataInputStream.readInt()); // 从文件中读取一个Double值 System.out.println("从文件中读取一个Double值:" + dataInputStream.readDouble()); // 从文件中读取一个Long值 System.out.println("从文件中读取一个Long值:" + dataInputStream.readLong()); // 从文件中读取一个UTF-8编码值 System.out.println("从文件中读取一个UTF-8编码值:" + dataInputStream.readUTF()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">   从文件中读取一个Int值:10</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">   从文件中读取一个Double值:0.98</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">   从文件中读取一个Long值:12</span></div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">   从文件中读取一个UTF-8编码值:我来自西安电子科技大学</span>  </td></tr></tbody></table> 本人菜鸟,大牛勿喷,有问题,欢迎留言。。。。
';

[Java开发之路](7)RandomAccessFile类详解

最后更新于:2022-04-01 09:59:39

RandomAccessFile适用于大小已知的记录组成的文件,提供的对文件访问,既可以读文件,也可以写文件,并且支持随机访问文件,可以访问文件的任意位置。文件中记录的大小不一定都相同,只要我们知道记录的大小和位置。但是该类仅限于操作文件。 RandomAccessFile不属于InputStream和OutputStream继承层次结构中的一部分。除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个继承层次结构没有任何关系,它甚至不使用InputStream和OutputStream类中已经存在的任何功能;它是一个完全独立的类,从头开始编写其所有的方法(大多数都是本地的)。这么做是因为RandomAccessFile拥有和别的IO类型本质上不同的行为,因为我们可以在一个文件内向前和向后移动。它是一个直接继承Object的,独立的类。 本质上说,RandomAccessFile的工作方式类似于把DataInputStream和DataOutputStream结合起来,还添加了一些方法,其中方法getFilePointer( )用来查找当前所处的文件位置,seek( )用来在文件内移至新的位置,length( )用来判断文件大小。此外,它的构造方法还需要一个参数来表示打开模式(只读方式 r 读写方式 rw),它不支持只写文件。 只有RandomAccessFile支持搜寻方法(seek()),并且这个方法也只适用于文件。BufferedInputStream却只能允许标注(mark())位置(其值存储在内部某个变量内)和重新设定位置(reset()),但是这些功能有限,不是非常实用。 在JDK 1.4中,RandomAccessFile的绝大多数功能(但不是全部)已经被nio内存映射文件给取代了。 **方法:** | 方法 | 描述 | |-----|-----| | void close() | 关闭此随机访问文件流并释放与该流关联的所有系统资源。 | | FileChannel getChannel () | 返回与此文件关联的唯一 FileChannel 对象。 | | FileDescriptor getFD () | 返回与此流关联的不透明文件描述符对象。 | | long getFilePointer () | 返回此文件中的当前偏移量,用来查找当前所处的位置。 | | long length() | 返回此文件的长度。 | | int read() | 从此文件中读取一个数据字节 | | int read(byte[] b) | 将最多 b.length 个数据字节从此文件读入 byte 数组。 | | int read(byte[] b,int off,int len) | 将最多 len 个数据字节从此文件读入 byte 数组。 | | boolean readBoolean() | 从此文件读取一个 boolean。 | | byte readByte() | 从此文件读取一个有符号的八位值。 | | char readChar() | 从此文件读取一个字符 | | double readDouble() | 从此文件读取一个 double。 | | float readFloat() | 从此文件读取一个 float。 | | void readFully(byte[] b) | 将 b.length 个字节从此文件读入 byte 数组,并从当前文件指针开始。 | | void readFully(byte[] b,int off,int len) | 将正好 len 个字节从此文件读入 byte 数组,并从当前文件指针开始。 | | int readInt() | 从此文件读取一个有符号的 32 位整数。 | | String readLine() | 从此文件读取文本的下一行。 | | long readLong() | 从此文件读取一个有符号的 64 位整数。 | | short readShort() | 从此文件读取一个有符号的 16 位数。 | | int readUnsignedByte() | 从此文件读取一个无符号的八位数 | | int readUnsignedShort() | 从此文件读取一个无符号的 16 位数。 | | String readUTF() | 从此文件读取一个字符串。 | | void seek(long pos) | 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。 | | void setLength(long newLength) | 设置此文件的长度。 | | int skipBytes(int n) | 尝试跳过输入的 n 个字节以丢弃跳过的字节。 | | void write(byte[] b) | 将 b.length 个字节从指定 byte 数组写入到此文件,并从当前文件指针开始。 | | void write(byte[] b, int off, int len) | 将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始。 | | void write(int b) | 向此文件写入指定的字节。 | | void writeBoolean(boolean v) | 按单字节值将 boolean 写入该文件。 | | void writeByte(int v) | 按单字节值将 byte 写入该文件 | | void writeBytes(String s) | 按字节序列将该字符串写入该文件。 | | void writeChar(int v) | 按双字节值将 char 写入该文件,先写高字节。 | | void writeChars(String s) | 按字符序列将一个字符串写入该文件。 | | void writeDouble(double v) | 使用 Double 类中的 doubleToLongBits 方法将双精度参数转换为一个 long,然后按八字节数量将该 long 值写入该文件,先定高字节。 | | void writeFloat(float v) | 使用 Float 类中的 floatToIntBits 方法将浮点参数转换为一个 int,然后按四字节数量将该 int 值写入该文件,先写高字节。 | | void writeInt(int v) | 按四个字节将 int 写入该文件,先写高字节。 | | void writeLong(long v) | 按八个字节将 long 写入该文件,先写高字节 | | void writeShort(int v) | 按两个字节将 short 写入该文件,先写高字节。 | | void writeUTF(String str) | 使用 modified UTF-8 编码以与机器无关的方式将一个字符串写入该文件。 | **案例:** ~~~ package com.qunar.bean; import java.io.File; import java.io.RandomAccessFile; import java.util.Arrays; public class FileDemo { public static void main(String[] args) { String pathname = "D:\\Recommended system.txt"; // 创建文件实例 File file = new File(pathname); try { // 判断文件是否存在 if(!file.exists()){ file.createNewFile(); }//if // 读写方式打开文件 RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); System.out.println("当前所处的位置:"+randomAccessFile.getFilePointer()); // write 从当前指针开始写入,写入一个字节 randomAccessFile.write('A'); System.out.println("当前所处的位置:"+randomAccessFile.getFilePointer()); randomAccessFile.write('B'); int num = 0x7fffffff; // 如果用write方法,每次只能写一个字节,需要写4次 randomAccessFile.write(num >>> 24); randomAccessFile.write(num >>> 16); randomAccessFile.write(num >>> 8); randomAccessFile.write(num); System.out.println("当前所处的位置:"+randomAccessFile.getFilePointer()); // 或者是用writeInt方法 一次写入 randomAccessFile.writeInt(num); System.out.println("当前所处的位置:"+randomAccessFile.getFilePointer()); // 文件指针指向文件开头 randomAccessFile.seek(0); // 一次性读取 把文件中内容都读到字节数组中 byte[] buffer = new byte[(int)randomAccessFile.length()]; randomAccessFile.read(buffer); for (byte b : buffer) { // 16进制输出 System.out.print(Integer.toHexString(b)+" "); }//for randomAccessFile.close(); } catch (Exception e) { e.printStackTrace(); } } } ~~~ ~~~ package com.qunar.bean; import java.io.File; import java.io.RandomAccessFile; public class FileDemo { public static void main(String[] args) { String pathname = "D:\\Recommended system.txt"; // 创建文件实例 File file = new File(pathname); try { // 判断文件是否存在 if(!file.exists()){ file.createNewFile(); }//if // 读写方式打开文件 RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); // 写值 for(int i = 0;i < 5;++i){ randomAccessFile.writeInt(i); }//for // 将文件指针移到第二个Int值后 randomAccessFile.seek(2*4); // 覆盖第三个Int值 randomAccessFile.writeInt(6); // 文件指针指向文件开头 randomAccessFile.seek(0); // 输出 for (int i = 0;i < 5;++i) { System.out.print(randomAccessFile.readInt()+" "); }//for randomAccessFile.close(); } catch (Exception e) { e.printStackTrace(); } } } ~~~ ~~~ package com.qunar.bean; import java.io.File; import java.io.RandomAccessFile; public class FileDemo { public static void main(String[] args) { String pathname = "D:\\Recommended system.txt"; // 创建文件实例 File file = new File(pathname); try { RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); // 以下向file文件中写数据 // 占4个字节 randomAccessFile.writeInt(2015); // 占8个字节 randomAccessFile.writeDouble(12.23); // 占2个字节 randomAccessFile.writeShort(19); System.out.println("当前位置:"+randomAccessFile.getFilePointer()); randomAccessFile.writeUTF("欢迎来到小斯的博客"); System.out.println("当前位置:"+randomAccessFile.getFilePointer()); // 占2个字节 randomAccessFile.writeChar('Y'); System.out.println("当前位置:"+randomAccessFile.getFilePointer()); randomAccessFile.writeUTF("小斯的博客欢迎你"); // 把文件指针位置设置到文件起始处 randomAccessFile.seek(0); System.out.println("读取一个Int值:"+randomAccessFile.readInt()); System.out.println("读取一个Double值:"+randomAccessFile.readDouble()); System.out.println("读取一个Short值:"+randomAccessFile.readShort()); System.out.println("读取一个字符串:"+randomAccessFile.readUTF()); // 将文件指针跳过2个字节 randomAccessFile.skipBytes(2); System.out.println("读取一个字符串:"+randomAccessFile.readUTF()); randomAccessFile.close(); } catch (Exception e) { e.printStackTrace(); } } } ~~~
';

[Java开发之路](6)File类的使用

最后更新于:2022-04-01 09:59:37

### 1. 构造方法 | **构造方法** | **描述** | |-----|-----| | File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建一个新的文件实例。 | | File(String parent , String child) | 通过给定的父路径名字符串和子路径名字符串来创建一个新的文件实例。 | | File(File parent , String child) | 通过给定的父抽象路径对象和子路径名字符串来创建一个新的文件实例。 | | File(URI uri) | 通过给定的URI来创建一个新的文件实例 | ~~~ package com.qunar.bean; import java.io.File; import java.net.URISyntaxException; public class FileDemo { public static void main(String[] args) throws URISyntaxException { String pathname = "E:\\Recommended system"; // File(String pathname) File file1 =new File(pathname); // File(String parent,String child) File file2 =new File(pathname,"train_data.txt"); // File(File parent,String child) File file3 = new File(file1, "train_data.txt"); // File(URI uri) // File file4 =new File(new URI("")); // separator 跨平台分隔符 File file4 =new File("E:"+File.separator+"Recommended system"); System.out.println(file1); System.out.println(file2); System.out.println(file3); System.out.println(file4); } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:919px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:918px"><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">   </span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">   E:\Recommended system</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">   E:\Recommended system\train_data.txt</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">   E:\Recommended system\train_data.txt</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">   E:\Recommended system</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑"><br/></span></div></td></tr></tbody></table> ### 2.创建与删除方法 <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:919px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:459px"><strong>方法</strong></td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:458px"><strong>描述</strong></td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:459px">boolean createNewFile()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">如果文件存在返回false,否则返回true并且创建文件 </td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:459px">boolean mkdir()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">创建目录</td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:459px">boolean mkdirs()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">创建多级目录</td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:459px">boolean delete()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">如果文件存在返回true并且删除文件,否则返回false</td></tr><tr><td colspan="1" style="border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:459px">boolean deleteOnExit()</td><td colspan="1" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">文件使用完成后删除</td></tr></tbody></table> ~~~ package com.qunar.bean; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; public class FileDemo { public static void main(String[] args) throws URISyntaxException { String pathname = "D:\\Recommended system.txt"; // 创建文件实例 File file = new File(pathname); try { // 如果文件存在返回false,否则返回true并且创建文件 if(file.createNewFile()){ System.out.println("创建文件["+pathname+"]"); }//if else{ System.out.println("文件["+pathname+"]已存在"); }//else } catch (IOException e) { e.printStackTrace(); } } } ~~~ ~~~ package com.qunar.bean; import java.io.File; public class FileDemo { public static void main(String[] args) { String pathname = "D:\\Recommended system.txt"; // 创建文件实例 File file = new File(pathname); //如果文件存在返回true并且删除文件,否则返回false if(file.delete()){ System.out.println("删除文件["+pathname+"]"); }//if else{ System.out.println("文件["+pathname+"]不存在"); }//else } } ~~~ ~~~ package com.qunar.bean; import java.io.File; public class FileDemo { public static void main(String[] args) { String pathname = "D:\\Recommended system"; String pathname2 = "D:\\Recommended system2\\Paper\\News"; // 创建文件实例 File file = new File(pathname); File file2 = new File(pathname2); //如果目录不存在返回true并且创建目录,否则返回false if(file.mkdir()){ System.out.println("创建目录["+pathname+"]"); }//if else{ System.out.println("目录["+pathname+"]已存在"); }//else //如果多级目录不存在返回true并且创建多级目录,否则返回false if(file2.mkdirs()){ System.out.println("创建多级目录["+pathname2+"]"); }//if else{ System.out.println("多级目录["+pathname2+"]已存在"); }//else } } ~~~ ### 3.判断方法 | **方法** | **描述** | |-----|-----| | boolean canExecute() | 判断文件是否可执行 | | boolean canRead() | 判断文件是否可读 | | boolean canWrite() | 判断文件是否可写 | | boolean exists() | 判断文件是否存在 | | boolean isDirectory() | 判断是否是目录 | | boolean isFile() | 判断是否是文件 | | boolean isHidden() | 判断是否隐藏 | | boolean isAbsolute() | 判断是否是绝对路径 文件不存在也能判断 | ~~~ package com.qunar.bean; import java.io.File; public class FileDemo { public static void main(String[] args) { String pathname = "D:\\Recommended system\\train_data.txt"; // 创建文件实例 File file = new File(pathname); // 判断文件是否可执行 if(file.canExecute()){ System.out.println("文件["+pathname+"]可执行"); }//if else{ System.out.println("文件["+pathname+"]不可执行"); }//else // 判断文件是否可读 if(file.canRead()){ System.out.println("文件["+pathname+"]可读"); }//if else{ System.out.println("文件["+pathname+"]不可读"); }//else // 判断文件是否可写 if(file.canWrite()){ System.out.println("文件["+pathname+"]可写"); }//if else{ System.out.println("文件["+pathname+"]不可写"); }//else // 判断文件是否存在 if(file.exists()){ System.out.println("文件["+pathname+"]存在"); }//if else{ System.out.println("文件["+pathname+"]不存在"); }//else // 判断文件是否是目录 if(file.isDirectory()){ System.out.println("文件["+pathname+"]是目录文件"); }//if else{ System.out.println("文件["+pathname+"]不是目录文件"); }//else // 判断是否是一个绝对路径 if(file.isAbsolute()){ System.out.println("["+pathname+"]是绝对路径"); }//if else{ System.out.println("["+pathname+"]不是绝对路径"); }//else } } ~~~ ### 4.获取方法 <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:919px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:459px"><strong>方法</strong></td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); text-align:center; width:458px"><strong>描述</strong></td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">String getName()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回文件或者是目录的名称</td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">String getPath()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回路径</td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">String getAbsolutePath()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回绝对路径</td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">String getParent()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回父目录,如果没有父目录则返回null</td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">long lastModified()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回最后一次修改的时间</td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">long length()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回文件的长度</td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">File[] liseRoots()</td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回系统可用的系统盘</td></tr><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">String[] list() </td><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回一个字符串数组,给定路径下的文件或目录名称字符串</td></tr><tr><td colspan="1" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">String[] list(FilenameFilter filter)</td><td colspan="1" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回满足过滤器要求的一个字符串数组</td></tr><tr><td colspan="1" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">File[]  listFiles()</td><td colspan="1" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回一个文件对象数组,给定路径下文件或目录</td></tr><tr><td colspan="1" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:459px">File[] listFiles(FilenameFilter filter)</td><td colspan="1" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:458px">返回满足过滤器要求的一个文件对象数组</td></tr></tbody></table> ~~~ package com.qunar.bean; import java.io.File; public class FileDemo { public static void main(String[] args) { String pathname = "E:\\Recommended system"; // 创建文件实例 File file = new File(pathname); // 返回文件或者目录的名称 System.out.println("GetName->"+file.getName()); // 返回路径 System.out.println("GetPath->"+file.getPath()); // 返回文件长度 System.out.println("Length->"+file.length()); // 返回给定路径下的文件和目录字符串数组 String[] files = file.list(); for (String name : files) { System.out.println("名称:"+name); }//for File[] files2 = file.listFiles(); for (File file2 : files2) { if(file2.isFile()){ System.out.println("文件名称:"+file2.getName()); }//if else if(file2.isDirectory()){ System.out.println("目录名称:"+file2.getName()); }//else }//for // 列出可用的系统盘 File[] files3 = file.listRoots(); for (File file3 : files3) { System.out.println("文件名称:"+file3.getPath()); } } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:919px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:918px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">GetName-&gt;Recommended system</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">GetPath-&gt;E:\Recommended system</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">Length-&gt;16384</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:540135b87c6d0.csv</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:5403c3df31780.doc</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:baidu-salon-38-1.mp3</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:baidu-salon-38-1.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:TextAnalysis.rar</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:train_data.txt</span></div><div><span style="color:windowtext; font-family:微软雅黑; font-size:14pt; line-height:1.5">名称:中文停用词表(比较全面_有1208个停用词).txt</span><br/></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:主题提起</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:协同过滤</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:天猫推荐算法.mp3</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:天猫推荐算法.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:推荐系统入门.zip</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:推荐系统实战经验与效果提升之道.pdf</span></div><div><br/></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:540135b87c6d0.csv</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:5403c3df31780.doc</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:baidu-salon-38-1.mp3</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:baidu-salon-38-1.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:TextAnalysis.rar</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:train_data.txt</span></div><div><span style="color:windowtext; font-family:微软雅黑; font-size:14pt; line-height:1.5">文件名称:中文停用词表(比较全面_有1208个停用词).txt</span><br/></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">目录名称:主题提起</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">目录名称:协同过滤</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:天猫推荐算法.mp3</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:天猫推荐算法.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:推荐系统入门.zip</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:推荐系统实战经验与效果提升之道.pdf</span></div><div><br/></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:C:\</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:D:\</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:E:\</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:F:\</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:G:\</span></div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">文件名称:H:\</span>  </td></tr></tbody></table> 文件过滤例子: ~~~ package com.qunar.bean; import java.io.File; import java.io.FilenameFilter; public class FileDemo { public static void main(String[] args) { String pathname = "E:\\Recommended system"; // 创建文件实例 File file = new File(pathname); // 文件过滤 File[] files = file.listFiles(new FilenameFilter() { @Override public boolean accept(File file, String filename) { return filename.endsWith(".mp3"); } }); // 打印输出 for (File file2 : files) { System.out.println("名称:"+file2.getName()); }//for } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:919px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:918px"><br/><div><span style="font-family:微软雅黑; font-size:10.5pt; line-height:1.5">    </span><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:baidu-salon-38-1.mp3</span></div><div><span style="font-family:微软雅黑; font-size:10.5pt; line-height:1.5">    </span><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:天猫推荐算法.mp3</span></div><span style="font-family:微软雅黑">    </span><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">名称:百度推荐实践.mp3</span>  </td></tr></tbody></table> 列出文件夹下的全部问价或目录: ~~~ package com.qunar.bean; import java.io.File; public class FileDemo { public static void ListAllFile(File file){ if(!file.exists()){ throw new IllegalArgumentException("目录["+file+"]不存在"); }//if if(!file.isDirectory()){ throw new IllegalArgumentException("["+file+"]不是目录"); }//if // 列出指定路径下的全部文件或目录 File[] files = file.listFiles(); for (File fileName : files) { // 判断是否是目录 如果是递归 if(fileName.isDirectory()){ ListAllFile(fileName); }//if // 否则打印输出 else{ System.out.println(fileName.getPath()); }//else }//for } public static void main(String[] args) { String pathname = "E:\\Recommended system"; // 创建文件实例 File file = new File(pathname); // 列出全部文件或目录 ListAllFile(file); } } ~~~ 运行结果: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:919px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:918px"><br/><blockquote style="margin:0px 0px 0px 40px; border:none; padding:0px"><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\540135b87c6d0.csv</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\5403c3df31780.doc</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\baidu-salon-38-1.mp3</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\baidu-salon-38-1.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\TextAnalysis.rar</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\train_data.txt</span></div><div><span style="color:windowtext; font-family:微软雅黑; font-size:14pt; line-height:1.5">E:\Recommended system\中文停用词表(比较全面_有1208个停用词).txt</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\主题提起\中文新闻关键事件的主题句识别.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\主题提起\加入时间因素的个性化信息过滤技术.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\主题提起\动态新闻主题信息推荐系统设计.pdf</span></div><div><span style="font-family:微软雅黑"><span style="font-size:19px; line-height:27px">...</span></span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\协同过滤\PatternRecognitionAndMachineLearning.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\协同过滤\一种改进的Item-based协同过滤推荐算法.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\协同过滤\一种电影个性化推荐系统的研究与实现.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\协同过滤\个性化搜索引擎中用户协作推荐算法的研究.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\协同过滤\个性化新闻推荐引擎中新闻分组聚类技术的研究与实现.caj</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\协同过滤\协同过滤技术在个性化推荐中的运用.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\协同过滤\基于协同过滤的个性化新闻推荐系统的研究与实现.caj</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\协同过滤\基于矩阵分解的协同过滤算法.pdf</span></div><div><span style="font-family:微软雅黑"><span style="font-size:19px; line-height:27px">....</span></span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\天猫推荐算法.mp3</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\天猫推荐算法.pdf</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微软雅黑">E:\Recommended system\推荐系统入门.zip</span></div><div>.<span style="font-family:微软雅黑; font-size:19px; line-height:27px">....</span></div></blockquote></td></tr></tbody></table>
';

[Java开发之路](5)异常详解

最后更新于:2022-04-01 09:59:34

### 1. 异常分类 在Java程序设计语言中,异常对象都是派生于Throwable类的一个实例。其是如果Java中的异常类不能满足需求,用户可以创建自己的异常类。 下图是Java异常层次结构的一个简化示意图。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a75b56e.jpg) 从图上可以看出,所有的异常都是继承于Throwable类,但是在下一层立即分解为两个分支:Error和Exception。 (1)Error Error描述了Java运行时系统的内部错误和资源耗尽错误。应用程序不应该抛出这种类型的错误,如果出现了这样的内部错误,除了通告用户,并尽力使程序安全的终止之外,再也无能为力了。这种情况很少见。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a771785.jpg) 这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,并且这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。 (2)Exception 程序设计者应该关注的是Exception,这一层次异常又分为两个分支:IOException和RuntimeException。划分这两个分支的规则是:由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但是由于IO错误这类问题导致的异常属于IOException。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a784038.jpg) 派生于RuntimeException的异常包括: - 异常的运算条件,如一个整数除以0时 - 错误的类型转换 - 数组访问越界 - 访问空指针 派生于IOException的异常包括: - 试图在文件尾部后面读取数据 - 试图打开一个不存在的文件 “如果出现RuntimeException异常则表明一定是你的问题”,这是一条相当有道理的规则。 <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1042px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187)"><br/>     注意:<br/>             异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。</td></tr></tbody></table> (3)Checked Exception 与 UnChecked Exception Java语言规范将派生于Error类或者RuntimeException类的所有异常称为未检查异常(UnChecked 异常),所有其他的异常(包括IOException)称为已检查异常(Checked 异常)。编译器将核查是否为所有的Checked 异常提供了异常处理器。 ### 2.声明已检查异常 如果遇到了无法处理的情况,那么Java的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。 例如:一段读取文件的代码知道优肯风读取的文件不存在,或者内容为空,因此,试图处理文件信息的代码就需要通知编译器可能会抛出IOException异常。 方法应该声明所有可能抛出的已检查异常,这样可以反映出这个方法可能抛出哪类已检查异常。 例如:下面是标准类库中提供的FileInputStream类的一个构造方法的声明异常情况: ~~~ public FileInputStream(String name) throws FileNotFoundException ~~~ 这个异常声明表示这个构造方法根据给定的字符串name正确情况下产生一个FileInputStream对象,但是也有可能抛出一个FileNotFoundException异常。如果抛出异常,方法不会初始化一个FileInputStream对象,而是抛出一个FileNotFoundException对象。抛出异常之后,运行时系统开始搜索异常处理器,以便知道如何处理 FileNotFoundException对象。 不是所有可能抛出的异常都必须进行声明,以下4种情况时记得抛出异常: - 调用一个抛出已检查异常的方法,如FileInputStream构造方法 - 程序运行过程中发现错误,并且利用throw语句抛出一个已检查异常 - 程序出现错误,例如,a[-1]=0会抛出一个下标越界的未检查异常 - Java虚拟机和运行库出现的内部错误 对于前两种情况必须进行声明。 不需要声明Java的内部错误,即从Error继承的错误,任何程序代码都具有抛出那些异常的潜能,但是它们在我们的控制范围之外。同样也不应该声明从RuntimeException继承的那些未检查异常。 ~~~ void Read(int index) throws ArrayIndexOutOfBoundsException // bad style { ... } ~~~ 这些运行时错误完全在我们的控制范围之内,如果特别关注数组下标引发的错误,就会将更多的时间花费在修正程序中的错误上,而不是说明这些错误发生的可能性上。 总结: <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1042px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1041px"><span style="color:#ff0000"><br/></span><ol><li><span style="color:#ff0000; font-size:10.5pt; line-height:1.5">一个方法必须声明所有可能抛出的已检查异常,而未检查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。</span><br/></li><li><span style="color:rgb(255,0,0); font-size:10.5pt; line-height:1.5"> 如果一个方法没有声明所有可能发生的已检查异常,编译器就会给出一个错误信息。</span><br/></li><li><span style="color:rgb(255,0,0); font-size:10.5pt; line-height:1.5">如果类中的一个方法声明会抛出一个异常,而这个异常是某个特定类的实例时,则这个方法就有可能抛出一个这个类,或者这个类一个子类的异常。</span></li></ol></td></tr></tbody></table> ### 3.如何抛出异常 假设有一个方法用来读取文件内容,给定的文本长度为1024,但是读到700个字符之后文件就结束了,我们认定这不是一种正常的情况,希望抛出异常。 首先决定应该抛出什么类型的异常(EOFException),知道之后抛出异常的语句如下: ~~~ // 第一种方法 throw new EOFException(); // 第二种方法 EOFException e = new EOFException(); throw e; ~~~ EOFException类还有一个含有一个字符串参数的构造方法,可以更加细致描述异常出现的状况: ~~~ String gripe = "未到指定长度,文件读取结束"; throw new EOFException(gripe); ~~~ 对于一个已经存在的异常类,抛出异常过程: - 找到一个合适的异常类 - 创建这个异常类的一个对象 - 将对象抛出 ### 4.创建异常类 在程序中,可能会遇到任何标准程序类都没有能够充分描述清楚的问题,这种情况下,我们需要创建我们自己的异常类。我们需要做的就是定义一个派生类于Exception,或者派生于Exception子类的类。习惯上,定义的类应该包含两个构造器,一个是默认的构造器,一个是带有详细描述信息的构造器。 ~~~ public class FileFormatException extends Exception{ /** * */ private static final long serialVersionUID = 1L; // 默认构造器 public FileFormatException(){ } // 带有详细描述信息的构造器 public FileFormatException(String gripe){ super(gripe); } } ~~~ 现在我们可以抛出我们自己定义的异常类型了。 ~~~ throw new FileFormatException; ~~~ ### 5.捕获异常 如果某个异常发生的时候没有任何地方进行捕获,那程序就会终止,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。 ~~~ package com.qunar.test; public class ExceptionTest { public static void main(String[] args) { int a = 10; int b = 0; System.out.printf("%d / %d = %d",a,b,a/b); System.out.println("测试结束..."); } } ~~~ 控制台信息: Exception in thread "main" java.lang.ArithmeticException: / by zero     at com.qunar.test.ExceptionTest.main(ExceptionTest.java:8) 从异常信息可以看出程序并没有运行完全,没有输出“测试结束...”,程序就终止,并且在控制台打印出异常信息。 要想捕获异常,必须使用try/catch语句块。 ~~~ try{ code more code more code } catch (Exception e) { handle for this type } ~~~ (1)如果在try语句块中任何代码抛出一个在catch子句中说明的异常类,那么: - 程序将跳过try语句块的剩余代码 - 程序将执行catch子句的处理器代码 (2)如果在try语句块中代码没有抛出异常,那么程序将跳过catch子句。 (3)如果方法中的任何代码抛出了一个在catch子句没有声明的异常类型,那么这个方法就会立刻退出。 ~~~ package com.qunar.test; public class ExceptionTest { public static void main(String[] args) { int a = 10; int b = 0; try{ System.out.printf("%d / %d = %d",a,b,a/b); } catch (ArithmeticException e) { System.out.println("a / b b 不能等于0"); } System.out.println("测试结束..."); } } ~~~ 运行结果: a / b  b 不能等于0 测试结束...   看一个例子:(读取文本程序代码) ~~~ public void read(String name){ try{ InputStream inputStream = new FileInputStream(name); int b; while((b = inputStream.read()) != -1){ // ... }//while } catch (IOException e) { e.printStackTrace(); } } ~~~ 对于一个普通的程序来说,这样的处理异常基本上合乎情理,但是,通常最好的情况是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去处理,如果采用这种处理方式,就必须声明这个方法可能会抛出一个IOException。 ~~~ public void read(String name) throws IOException{ InputStream inputStream = new FileInputStream(name); int b; while((b = inputStream.read()) != -1){ // ... }//while } ~~~ 如果调用了一个抛出已检查异常的方法,就必须对它进行处理或者将它继续进行传递。 出现了两种处理方式,那到底哪种方式更好呢? 通常,应该捕获那些知道如何处理的异常,而将那些不知道怎么处理的异常继续进行传递。如果想传递一个异常,就必须在方法添加一个throws说明符。仔细阅读Java API文档,以便知道每个方法可能会抛出哪种异常,然后再决定是自己处理,还是加到throws列表中。 ### 6.捕获多个异常 在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。 ~~~ try{ } catch (FileNotFoundException e) { // emergency action for missing files } catch (UnknownException e) { // emergency action for unknown hosts } catch (IOException e) { // emergency action for all other I/O problems } ~~~ ### 7.再次抛出异常与异常链 在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。如果开发了一个供其他程序员使用的子系统,那么用于表示系统故障的异常类型会有多种解释。ServletException就是这样一个异常类型的例子。执行Servlet的代码可能不想知道发生的错误的细节原因,但希望知道servlet是否有问题。 下面给出了捕获异常并将它再次抛出的基本方法: ~~~ try{ access the database } catch(SQLException e){// 发生错误的细节原因 throw new ServletException("database error:"+e.getMessage());// 只希望知道servlet是否有问题 } ~~~ 再给出一个更好的方法:包装技术 ~~~ try{ access the databse } catch(SQLException e){ Throwable se = new ServletException("database error"); // 设置初始异常 se.initCause(e); throw se; } ~~~ 这个方法更好的地方在于当捕获到异常时可以使用下面语句得到原始异常(不会丢失原始异常的细节): ~~~ Throwable e = se.getCause(); ~~~ 这种方法还可以解决一下问题: 如果一个方法中发生了一个已检查异常,而不允许抛出它,那么包装技术就十分有用,我们可以捕获这个已检查异常,并把它包装成一个运行时异常。 ### 8.finally子句 当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法知道,同时这些资源再退出之前必须回收。这就需要finally子句来解决。不管是否有异常,finally子句的代码都被执行。 举个例子,当发生异常时,恰当的关闭所有数据库的链接是非常重要的,这种情况就可以使用finally。 ~~~ try{ } catch (Exception e) { } finally{ } ~~~ (1)如果代码没有抛出异常。首先执行try语句块中的全部代码,然后执行finally子句中的代码。 (2)如果代码抛出异常,并且在catch子句可以捕获到。首先执行try语句块汇总的所有代码,直到发生异常为止,此时跳过try语句块中剩余代码,去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。 (3)如果代码抛出异常,但这个异常不是由catch子句捕获的。首先执行try语句块汇总的所有代码,直到发生异常为止,此时跳过try语句块中剩余代码,然后执行finally子句中的代码,并将异常抛给这个方法的调用者。 建议独立使用try/catch和try/finally语句块。这样可以提高代码的清晰度。例如: ~~~ package com.qunar.test; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class ExceptionTest { public static void main(String[] args) { String name = ""; InputStream in = null; // 确保报告出现的错误 try{ in = new FileInputStream(name); // 确保关闭输入流 try{ // code that might throw exceptions } finally{ in.close(); } } catch (IOException e) { // show error message } } } ~~~ 内层的try语句块只有一个职责,就是确保关闭输入流。外层的try语句块也只有一个职责,就是确保报告出现的错误。这种设计不仅清楚,而且还具有一个功能,就是将会报告finally子句中出现的错误。 当finally子句包含return语句时,会出现意想不到的结果。假设利用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值。 ~~~ package com.qunar.test; public class ExceptionTest { public static int function(int n){ try{ int r = n * n; return r; } finally{ if(n == 2){ return 0; }//if }//finally } public static void main(String[] args) { System.out.println(function(2)); } } ~~~ 如果调用function(2),那么try语句块中计算结果为4,并执行return语句。但是在方法真正返回前,还要执行finally子句,将使得方法返回0,覆盖了原始的返回值4。 有时候,finally也会有麻烦。 ~~~ InputStream in = ...; try{ // code that might throw exceptions } finally{ in.close(); } ~~~ 假设在try语句块抛出了一个非IOException的异常,这个异常只有方法的调用者才能处理。执行finally子句,并调用in.close()方法,而close方法本身也可能抛出IOException异常。当这种情况出现时,原始的异常将会丢失,转而抛出clsoe方法的异常。
';

[Java开发之路](4)String、StringBuffer与StringBuilder详解

最后更新于:2022-04-01 09:59:32

最近学习到字符串,整理了一下String,StringBuffer,StringBuilder相关知识 ### 1. String String 类位于 java.lang 包中。String 对象创建后则不能被修改,是不可变的,所谓的修改其实是创建了新的对象,所指向的内存空间不同。 ~~~ String str1 = "xiaosi"; str1 = "欢迎你 " + str1; System.out.println(str1); // 欢迎你 xiaosi ~~~ 通过观察运行结果可以看见str1变量输出确实改变了,但是为什么一直说String对象是不可变的呢? 我们其实被表象给欺骗了,JVM是这样解析这段代码的:首先创建对象str1,赋予xiaosi,然后再创建一个新的对象用来执行第二行代码,str1指向了这个新对象,原对象没有发生改变,由于这种机制,每当用String操作字符串时,实际上是在不断的创建新对象,而原来的对象就会变为垃圾被GC回收掉。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eba2a73fde6.jpg) 这样做是否会降低运行效率呢?看起来好像修改一个代码单元要比创建一个新字符串更加简洁。答案是:也对,也不对。通过拼接"欢迎你"和"xiaosi"来创建一个新字符串的效率确实不高。但是,不可变字符却有一个有点:编译器可以让字符串达到共享。 为了弄清楚具体的工作方式,可以想象将各种字符串放入公共的字符串存储池中,字符串变量指向字符串存储池中相应位置。如果复制一个字符串变量,原始字符串与复制的字符串变量共享相同的字符。 Java设计者认为共享带来的高效率远远胜于提取,拼接字符串所带来的低效率。查看一下程序你就会发现:很少需要修改字符串,而是往往需要对字符串进行比较。 ### 2.StringBuffer String和StringBuffer他们都可以存储和操作字符串,即包含多个字符的字符串数据。String类是字符串常量,是不可更改的常量。而StringBuffer是字符串变量,它的对象是可以扩充和修改的。 ~~~ // 创建一个空的StringBuffer类的对象。 public StringBuffer() // 创建一个长度为 参数length 的StringBuffer类的对象。 public StringBuffer( int length ) ~~~ 其是线程安全的可变字符序列。一个类似于 String 的字符串缓冲区。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。 StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符添加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。 每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。 ### 3.StringBuilder 从 JDK 5 开始,为该类补充了一个单个线程使用的等价类,即 StringBuilder。StringBuffer和StringBuilder类功能基本相似,主要区别在于StringBuffer类的方法是多线程、安全的,而 StringBuilder不是线程安全的。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。对于经常要改变值的字符串应该使用 StringBuffer和StringBuilder类。 ### 4.总结 (1)一般情况下,速度从快到慢:StringBuilder > StringBuffer > String,这种比较是相对的,不是绝对的。  (2)当字符串缓冲被多个线程使用时,JVM不能保证StringBuilder的操作是安全的,虽然速度快,但是StringBuffer是可以保证是正确操作的。大多数情况下是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的。
';