第8章 集合
集合是编程中常用的一种数据结构,用于存取对象。由于数组只支持单类型元素,存储的数据的数据类型必须相同,所以在很多的使用中存在限制。集合提供大量的接口、类以及方法供用户选择使用,方便地解决了这一难题。集合可以轻松实现数据元素的查找、添加、迭代、插入或删除指定位置的元素以及清空等操作。
本章介绍最常用的两类集合工具:一是集合类,实现java.util.Collection接口,可容纳多个彼此独立的对象,以列表(List)和集合(Set)为代表;二是映射(Map),实现java.util.Map或Diretionary类,以容纳多个键—值(key-value)形式的数据。
灵活运用集合,可以省去许多数据结构方面的计算,使读者达到事半功倍的效果。
实例63 谁养鱼(运用ArrayList)
有这样一道逻辑性很强的题:在一条街上有五座房子,喷了五种颜色,在每个房里住着不同国籍的人,每个人喝不同的饮料,抽不同品牌的香烟,养不同的宠物,问谁养的是鱼?提示:英国人住红色房子,瑞典人养狗,丹麦人喝茶,绿色房子在白色房子左面,绿色房子主人喝咖啡,抽PalMal香烟的人养鸟,黄色房子主人抽Dunhill香烟,住在中间房子的人喝牛奶,挪威人住第一间房,抽Blends香烟的人住在养猫的人的隔壁,养马的人住抽Dunhill香烟的人隔壁,抽BlMt的人喝啤酒,德国人抽Prince香烟,挪威人住蓝色房子的隔壁,抽Blends香烟的人有一个喝水的邻居。
技术要点
运用ArrayList向量表计算谁养鱼的技术要点如下:
• ArrayList是List接口的一个可变长数组实现,实现了所有List接口的操作,并允许存储空(null)值。除没有进行同步,ArrayList基本等同于Vector。在Vector中几乎对所有的方法都进行同步,但ArrayList仅对writeObject()和readObject()进行同步。其他如add()、remove()等都没有同步。
• ArrayList实现了java.io.Serializable接口,所以ArrayList对象可以序列化到持久存储介质中。
• 实例化一个ArrayList对象时,可以指定一个初始容量,默认的容量为10。当元素超过ArrayList对象的初始大小时,容器容量大小增加原来的50%。
实现步骤
(1)新建一个类名为TextArrayList.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 import java.util.ArrayList; //引入类 public class TextArrayList { //操作使用ArrayList判断谁养鱼 private static String[] HOUSES = {"红房子", "白房子", "绿房子", "蓝房子", "黄房子"}; private static String[] PERSONS = {"英国人", "瑞典人", "丹麦人", "挪威人", "德国人"}; private static String[] DRINKS = {"茶", "咖啡", "牛奶", "啤酒", "水" }; private static String[] SMOKES = {"PalMal", "Dunhill", "BlMt", "Prince","Blends"}; private static String[] PETS = { "狗", "鸟", "猫", "马", "鱼" }; private int[][] color; //颜色数组 private int[][] person; //人员数组 private int[][] drink; //饮料数组 private int[][] smoke; //烟数组 private int[][] pet; //宠物数组 private int total = 0; public void init() { //计算一组数据的组合方式 ArrayList array = new ArrayList(); //创建集合数组 for (int num1 = 0; num1 < 5; num1++) { for (int num2 = 0; num2 < 5; num2++) { if (num2 == num1) continue; for (int num3 = 0; num3 < 5; num3++) { if (num3 == num2 || num3 == num1) continue; for (int num4 = 0; num4 < 5; num4++) { if (num4 == num3 || num4 == num2 || num4 == num1) continue; for (int num5 = 0; num5 < 5; num5++) { if (num5 == num4 || num5 == num3 || num5 == num2 || num5 == num1) continue; int oneArray[] = { num1, num2, num3, num4, num5 }; array.add(oneArray); } } } } } color = new int[array.size()][5]; //创建颜色的二维数组 for (int count = 0; count < array.size(); count++){ //循环数组时初始化房子颜色数据 color[count] = (int[]) array.get(count); } person = color; drink = color; smoke = color; pet = color; } public void calculate() { //判断运算 init(); //调用方法时初始化数据 for (int num1 = 0; num1 < color.length; num1++) { if (!con4(num1)) continue; if (!con14(num1)) continue; for (int num2 = 0; num2 < person.length; num2++) { if (!con1(num2, num1)) continue; if (!con8(num2)) continue; for (int num3 = 0; num3 < drink.length; num3++) { if (!con3(num2, num3)) continue; if (!con5(num1, num3)) continue; if (!con9(num3)) continue; for (int num4 = 0; num4 < smoke.length; num4++) { if (!con7(num1, num4)) continue; if (!con12(num4, num3)) continue; if (!con13(num2, num4)) continue; if (!con15(num4, num3)) continue; for (int num5 = 0; num5 < pet.length; num5++) { if (!con2(num2, num5)) continue; if (!con6(num4, num5)) continue; if (!con10(num4, num5)) continue; if (!con11(num5, num4)) continue; total++; show(num1, num2, num3, num4, num5); } } } } } } public boolean con1(int cy, int cl) { //英国人住红色房子 for (int i = 0; i < 5; i++) { if (person[cl][i] == 0) { if (color[cy][i] == 0) { return true; } else break; } } return false; } public boolean con2(int cy, int p) { //瑞典人养狗 for (int i = 0; i < 5; i++) { if (person[cy][i] == 1) { if (pet[p][i] == 0) { return true; } else break; } } return false; } public boolean con3(int cy, int d) { //丹麦人喝茶 for (int i = 0; i < 5; i++) { if (person[cy][i] == 2) { if (drink[d][i] == 0) { return true; } else break; } } return false; } public boolean con4(int cl) { //绿色房子在白色房子左面 int c1 = 0; //白房子 int c2 = 0; //绿房子 for (int i = 0; i < 5; i++) { if (color[cl][i] == 1) { c1 = i; } if (color[cl][i] == 2) { c2 = i; } } if (c2 < c1) return true; else return false; } public boolean con5(int cl, int d) { //绿色房子主人喝咖啡 for (int i = 0; i < 5; i++) { if (color[cl][i] == 2) { if (drink[d][i] == 1) { return true; } else break; } } return false; } public boolean con6(int s, int p) { //抽PalMal香烟的人养鸟 for (int i = 0; i < 5; i++) { if (smoke[s][i] == 0) { if (pet[p][i] == 1) { return true; } else break; } } return false; } public boolean con7(int cl, int s) { //黄色房子主人抽Dunhill香烟 for (int i = 0; i < 5; i++) { if (color[cl][i] == 4) { if (smoke[s][i] == 1) { return true; } else break; } } return false; } public boolean con8(int cy) { //住在中间房子的人喝牛奶 if (person[cy][0] == 3) return true; else return false; } public boolean con9(int d) { //挪威人住第一间房 if (drink[d][2] == 2) return true; else return false; } public boolean con10(int s, int p) { //抽Blends香烟的人住在养猫的人隔壁 for (int i = 0; i < 5; i++) { if (smoke[s][i] == 4) { if (i < 4 && pet[p][i + 1] == 2) { return true; } if (i > 0 && pet[p][i - 1] == 2) { return true; } break; } } return false; } public boolean con11(int p, int s) { //养马的人住抽Dunhill香烟的人隔壁 for (int i = 0; i < 5; i++) { if (pet[p][i] == 3) { if (i < 4 && smoke[s][i + 1] == 1) { return true; } if (i > 0 && smoke[s][i - 1] == 1) { return true; } break; } } return false; } public boolean con12(int s, int d) { //抽BlMt的人喝啤酒 for (int i = 0; i < 5; i++) { if (smoke[s][i] == 2) { if (drink[d][i] == 3) { return true; } else break; } } return false; } public boolean con13(int cy, int s) { //德国人抽Prince香烟 for (int i = 0; i < 5; i++) { if (person[cy][i] == 4) { if (smoke[s][i] == 3) { return true; } else break; } } return false; } public boolean con14(int c) { //挪威人住蓝色房子隔壁 if (color[c][1] == 3) return true; else return false; } public boolean con15(int s, int d) { //抽Blends香烟的人有一个喝水的邻居 for (int i = 0; i < 5; i++) { if (smoke[s][i] == 4) { if (i < 4 && drink[d][i + 1] == 4) { return true; } if (i > 0 && drink[d][i - 1] == 4) { return true; } break; } } return false; } publicvoidshow(intn1,intn2,intn3,intn4,intn5){//显示计算之后的每个数组找出对应答案 System.out.println("第" + total + "组:>"); System.out.println("1\t\t2\t\t3\t\t4\t\t5\t\t"); for (int i = 0; i < 5; i++) //循环显示房子数组数据 System.out.print(HOUSES[color[n1][i]] + "\t\t"); System.out.println(); for (int i = 0; i < 5; i++) //循环显示人员数组数据 System.out.print(PERSONS[person[n2][i]] + "\t\t"); System.out.println(); for (int i = 0; i < 5; i++) //循环显示饮料数组数据 System.out.print(DRINKS[drink[n3][i]] + "\t\t"); System.out.println(); for (int i = 0; i < 5; i++) //循环显示烟数组数据 System.out.print(SMOKES[smoke[n4][i]] + "\t\t"); System.out.println(); for (int i = 0; i < 5; i++) //循环显示宠物数组数据 System.out.print(PETS[pet[n5][i]] + "\t\t"); System.out.println(); } public static void main(String args[]) { //Java程序主入口处 TextArrayList test = new TextArrayList(); //实例化对象 long l = System.currentTimeMillis(); //获得系统时间 test.calculate(); //调用方法进行计算统计 System.out.println("计算共用时:" +(System.currentTimeMillis() - l) + "ms"); } }
(3)运行结果如下所示:
第1组: 1 2 3 4 5 绿房子 蓝房子 红房子 黄房子 白房子 挪威人 德国人 英国人 丹麦人 瑞典人 咖啡 水 牛奶 茶 啤酒 PalMal Prince Blends Dunhill BlMt 鸟 猫 马 鱼 狗 第2组: 1 2 3 4 5 绿房子 蓝房子 红房子 黄房子 白房子 挪威人 德国人 英国人 丹麦人 瑞典人 咖啡 水 牛奶 茶 啤酒 PalMal Prince Blends Dunhill BlMt 鸟 鱼 马 猫 狗 ...... 第7组: 1 2 3 4 5 黄房子 蓝房子 红房子 绿房子 白房子 挪威人 丹麦人 英国人 德国人 瑞典人 水 茶 牛奶 咖啡 啤酒 Dunhill Blends PalMal Prince BlMt 猫 马 鸟 鱼 狗 计算共用时:16ms
源程序解读
(1)init()方法中创建ArrayList列表集合,运用多种循环进行判断获得各组数据之间的组合方式,并将符合要求的组合方式放入列表集合中。循环遍历列表集合,将元素放入颜色、人员、饮料等数组中。
(2)calculate()方法调用init()方法对数组元素进行初始化。循环判断比较各数组元素的关系获得符合原题要求的元素的索引,再根据show()方法循环将指定索引的元素打印到控制台。
实例64 查看书目(运用Iterator)
迭代器(Iterator)是一种设计模式,它是一个对象,可以遍历并选择序列中的对象。迭代器通常被称为“轻量级”对象,因为创建它的代价小。本实例介绍如何使用迭代器以及其使用的规则。
技术要点
使用Iterator的技术要点如下:
• Iterator是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。
• Iterator使用时只能单向移动。使用iterator()方法返回一个Iterator对象,iterator()方法是java.lang.Iterable接口,被Collection继承。第一次调用Iterator的next()方法时,它返回序列的第一个元素。使用hasNext()方法检查序列中是否还有元素。Remove()方法将迭代器返回的元素删除。
• Iterator支持以不同的方式遍历一个聚合,复杂的聚合可用多种方式进行遍历。在同一个聚合上可以有多个遍历,每个迭代器保持自己的遍历状态。因此可以同时进行多个遍历。
实现步骤
(1)创建一个类名为TextIterator.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 import java.util.ArrayList; //引入类 import java.util.Iterator; import java.util.List; import java.util.Vector; public class TextIterator { //操作使用Iterator的类 public static void searchBooks(){ //查看书目 List<String> list=new ArrayList<String>(5); //创建容量为5的列表集合 list.add("Java入门提高"); //添加元素(对象) list.add("ASP.NET网络开发"); list.add("JavaScript开发技术大全"); list.add("PHP程序设计"); System.out.println("第一次查看书目:"); for(Iterator iter=list.iterator();iter.hasNext();){//使用Iterator进行循环 Object obj=iter.next(); //获得每个元素(对象) System.out.print(obj+"\t"); if("PHP程序设计".equals(obj)) //判断 iter.remove(); //移除对象 } System.out.println(); System.out.println("第二次查看书目:"); Iterator it=list.iterator(); //获得Iterator对象 while(it.hasNext()){ //只要有元素(对象)便进行循环 System.out.print(it.next()+"\t"); } System.out.println(); } public static void searchResult(){ //查看经过一系列操作后的书目 Vector<String> vector=new Vector<String>(4); //创建容量为4的向量集合 vector.add("Java入门提高"); //添加元素(对象) vector.add("ASP.NET网络开发"); vector.add("JavaScript开发技术大全"); vector.add("PHP程序设计"); System.out.println("查看经过操作后的书目:"); for(Iterator iter=vector.iterator();iter.hasNext();){//使用Iterator进行循环 if(iter.next().equals("Java入门提高")) //获得一个元素进行判断 iter.remove(); //移除对象 else{ System.out.println(iter.next().toString()); //输出元素 } } } public static void main(String []args){ //Java程序主入口处 searchBooks(); //调用方法获得书目 searchResult(); //调用方法获得操作后的书目 } }
(3)运行结果如下所示:
第一次查看书目: Java入门提高 ASP.NET网络开发 JavaScript开发技术大全 PHP程序设计 第二次查看书目: Java入门提高 ASP.NET网络开发 JavaScript开发技术大全 查看经过操作后的书目: JavaScript开发技术大全 Exception in thread "main" java.util.NoSuchElementException at java.util.AbstractList$Itr.next(Unknown Source) at com.zf.s8.TextIterator.searchResult(TextIterator.java:40) at com.zf.s8.TextIterator.main(TextIterator.java:46)
源程序解读
(1)searchBooks()方法创建列表集合并运用Java5.0新特性:泛型。<String>表示列表中只能存放字符串类型的数据。列表集合运用add()方法添加元素,运用iterator()方法创建Iterator对象。Iterator对象中的hasNext()方法控制列表集合的循环,next()方法依次获得每个列表集合中的每个元素,remove()方法删除列表集合中的元素。
(2)searchResult()方法创建向量集合并运用Java5.0新特性:泛型。<String>表示向量中只能存放字符串类型的数据。方法返回的结果是输出下标为2的元素,原因是:第一次循环用iter.next()方法,值为“Java入门提高”,调用iter.remove()方法将其移除;第二次循环调用iter.next()方法,值为“ASP.NET网络开发”,进入else语句,又调用一次iter.next()方法将“JavaScript开发技术大全”输出;第三次循环调用iter.next()方法,值为“PHP程序设计”,进入else语句,此时向量vector中已无更多元素,再调用next()方法则抛出异常。
实例65 操作元素(运用Vector)
向量(Vector)是另一种形式的列表(List)。Vector相对于ArrayList最重要的特征就是Vector是线程安全的。本实例介绍如何运用Vector对数组元素进行插入、修改、删除以及查询等操作。需要到官方网站:http://Java.sun.com/products/javamail/index.html下载开发用的jar文件。文件下载解压缩后,将mail.jar包放入项目的类库中。
技术要点
运用Vector操作数组元素的技术要点如下:
• Java.util.Vector提供了向量(Vector)类以实现类似动态数组的功能。创建一个向量类的对象后,可以往其中随意地插入不同的类的对象,既不需要顾及类型也不需要预先选定向量的容量,并可方便地进行查找。Vector的容量是自增的,当容量用完的时候,Vector以100%容量的速度递增。
• Vector内部提供了一种线程同步机制,当线程A在对Vector元素进行操作时,会制止线程B对Vector的访问。因此Vector不存在ArrayList线程不安全的问题,它是线程安全的。线程安全的代价就是运行速度比ArrayList慢了些。
实现步骤
(1)创建一个类名为TextVector.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 import java.util.Vector; //引入类 public class TextVector { //操作使用Vector的类 public Vector createVector() { //生成一个5*5的二维Vector Vector vector = new Vector(); //创建向量对象 for (int i = 0; i < 5; i++) { //双重循环往向量集合中添加数据 Vector v = new Vector(); for (int j = 0; j < 5; j++) { v.addElement("Vector(" + i + ")(" + j + ")"); } vector.addElement(v); } return vector; } public Vector insert(Vector vector, int index, Object obj) {//在指定位置插入元素 if (index > vector.size()) { print("传入索引超过向量集合长度!"); return null; } else { vector.insertElementAt(obj, index); //调用方法在指定位置插入元素 } return vector; } public Vector delete(Vector vector, int index) { //移除指定位置的元素 if (index > vector.size()) { print("传入索引超过向量集合长度!"); return null; } else { vector.removeElementAt(index); //移除指定位置的元素 } return vector; } public Vector update(Vector vector, int index, Object obj) {//修改向量集合数据 if (index > vector.size()) { print("传入索引超过向量集合长度!"); return null; } else { vector.setElementAt(obj, index); } return vector; } public void print(String str, Vector vector) { //输出信息 System.out.println(str + "数据:"); this.print(vector); } public void print(Object obj) { //打印输出重载 System.out.println(obj); } public void print(Vector vector) { //输出信息(重载) for (int i = 0; i < vector.size(); i++) { System.out.println(vector.elementAt(i)); } } public static void main(String[] args) { //Java程序主入口处 TextVector ov = new TextVector(); //实例化对象 Vector vector = ov.createVector(); //调用方法获得向量集合 ov.print("1.显示向量集合的二维数组", vector); //调用方法显示集合的信息 Vector iResult = ov.insert(vector, 3, "添加的数据"); ov.print("2.显示插入后向量集合数组", iResult); //调用方法显示集合的信息 Vector uResult = ov.update(iResult, 3, "修改的数据"); ov.print("3.显示修改后向量集合数组", uResult); //调用方法显示集合的信息 Vector dResult = ov.delete(uResult, 3); ov.print("4.显示删除后向量集合数组", dResult); //调用方法显示集合的信息 } }
(3)运行结果如下所示:
1. 显示向量集合的二维数组数据: [Vector(0)(0), Vector(0)(1), Vector(0)(2), Vector(0)(3), Vector(0)(4)] [Vector(1)(0), Vector(1)(1), Vector(1)(2), Vector(1)(3), Vector(1)(4)] [Vector(2)(0), Vector(2)(1), Vector(2)(2), Vector(2)(3), Vector(2)(4)] [Vector(3)(0), Vector(3)(1), Vector(3)(2), Vector(3)(3), Vector(3)(4)] [Vector(4)(0), Vector(4)(1), Vector(4)(2), Vector(4)(3), Vector(4)(4)] 2. 显示插入后向量集合数组数据: [Vector(0)(0), Vector(0)(1), Vector(0)(2), Vector(0)(3), Vector(0)(4)] [Vector(1)(0), Vector(1)(1), Vector(1)(2), Vector(1)(3), Vector(1)(4)] [Vector(2)(0), Vector(2)(1), Vector(2)(2), Vector(2)(3), Vector(2)(4)] 添加的数据 [Vector(3)(0), Vector(3)(1), Vector(3)(2), Vector(3)(3), Vector(3)(4)] [Vector(4)(0), Vector(4)(1), Vector(4)(2), Vector(4)(3), Vector(4)(4)] 3. 显示修改后向量集合数组数据: [Vector(0)(0), Vector(0)(1), Vector(0)(2), Vector(0)(3), Vector(0)(4)] [Vector(1)(0), Vector(1)(1), Vector(1)(2), Vector(1)(3), Vector(1)(4)] [Vector(2)(0), Vector(2)(1), Vector(2)(2), Vector(2)(3), Vector(2)(4)] 修改的数据 [Vector(3)(0), Vector(3)(1), Vector(3)(2), Vector(3)(3), Vector(3)(4)] [Vector(4)(0), Vector(4)(1), Vector(4)(2), Vector(4)(3), Vector(4)(4)] 4. 显示删除后向量集合数组数据: [Vector(0)(0), Vector(0)(1), Vector(0)(2), Vector(0)(3), Vector(0)(4)] [Vector(1)(0), Vector(1)(1), Vector(1)(2), Vector(1)(3), Vector(1)(4)] [Vector(2)(0), Vector(2)(1), Vector(2)(2), Vector(2)(3), Vector(2)(4)] [Vector(3)(0), Vector(3)(1), Vector(3)(2), Vector(3)(3), Vector(3)(4)] [Vector(4)(0), Vector(4)(1), Vector(4)(2), Vector(4)(3), Vector(4)(4)]
源程序解读
(1)createVector()方法创建向量集合实例并运用双重循环将二维向量对象通过addElement()方法放入向量集合中。
(2)insert()方法先判断指定的元素位置是否越界,再根据insertElementAt()方法将传入的对象插入指定的位置。
(3)delete()方法先判断指定的元素位置是否越界,再根据removeElementAt()方法删除指定位置的元素。其中removeElementAt()方法中的参数是元素的索引(或下标),不是指定删除的元素。
(4)update()方法先判断指定的元素位置是否越界,再根据setElementAt()方法对指定位置的元素重新赋值。
(5)两个print()方法根据所传参数的不同实现多态的功能。其中参数类型为Vector的print()方法循环遍历向量集合,运用elementAt()方法输出指定的元素。
实例66 栈和队列(运用LinkedList)
栈和队列是两种特殊的线性表,它们的逻辑结构和线性表相同,只是其运算规则较线性表有更多的限制,故又称它们为运算受限的线性表。本实例介绍运用双向链表LinkedList实现栈和队列。
技术要点
运用LinkedList实现栈和队列的技术要点如下:
• LinkedList实现List接口,允许null元素。LinkedList提供额外的get、remove、insert方法在LinkedList的首部或尾部,可以使LinkedList被用作堆栈(stack)、队列(queue)或双向队列(deque)。
• LinkedList数据结构是一种双向的链式结构,每一个对象除了数据本身外,还有两个引用,分别指向前一个元素和后一个元素。与数组的顺序存储结构,如ArrayList相比,插入和删除比较方便,但速度会慢一些。
• LinkedList没有同步方法,如果多个线程同时访问一个LinkedList,则必须自己实现访问同步,解决方法如:LinkedList list=Collections.synchronizedList(new LinkedList(...));。
实现步骤
(1)新建一个类名为TextLinkedList.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 abstract class MyList { protected int size = 0; //长度 protected MyList() { //默认构造方法 } abstract public boolean add(Object o); //抽象向链表末尾添加一个元素 abstract public Object get(int index); //获得指定元素 abstract public boolean contains(Object o); //判断是否包含元素 abstract int indexOf(Object o); //判断元素的位置 abstract public boolean remove(Object o); //移除元素 abstract public void clear(); //清空 public int size() { //获得长度大小 return this.size; } public boolean isEmpty() { //判断是否为空 return this.size == 0; } } interface Queue { //队列接口 void enqueue(String o); //入队 Object dequeue(); //出队 boolean isEmpty(); //判断是否为空 } interface Stack { //栈接口 void push(Object o); //元素入栈 Object pop(); //元素出栈 boolean isEmpty(); //是否为空 } class LinkedList extends MyList implements Queue, Stack { private static class MyEntry<E> { //静态内部类 Object object; //对象 MyEntry next; //下一个对象 MyEntry previous; //上一个对象 //带参数的构造方法进行初始化 MyEntry(Object object, MyEntry next, MyEntry previous) { this.object = object; this.next = next; this.previous = previous; } } private MyEntry header = new MyEntry(null, null, null); //创建实体对象 public LinkedList() { //默认构造方法进行初始化 this.header.next = this.header.previous = this.header; } private MyEntry addBefore(Object o, MyEntry e) { //添加对象之前的操作方法 MyEntry newEntry = new MyEntry(o, e, e.previous); //创建实体对象 newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; this.size++; return newEntry; } public boolean add(Object o) { //添加对象 this.addBefore(o, header); return true; } public void clear() { //清空对象 MyEntry e = this.header.next; while (e != this.header) { //判断进行循环 MyEntry next = e.next; e.next = e.previous = null; //清空对象 e.object = null; //清空对象 e = next; } this.header.next = this.header.previous = this.header; this.size = 0; }
public boolean contains(Object o) { //是否包含对象 return this.indexOf(o) != -1; } public Object get(int index) { //获得指定的对象 MyEntry<Object> myEntry = this.entry(index); if (myEntry == null) return null; return myEntry.object; } public int indexOf(Object o) { //获得对象在栈或队列中的位置 int index = 0; if (o == null) { for (MyEntry e = this.header.next; e != this.header; e = e.next) { //循环获得元素对象 if (e.object == null) return index; index++; } } else { for (MyEntry e = this.header.next; e != this.header; e = e.next) { //循环获得元素对象 if (o.equals(e.object)) return index; index++; } } return -1; } public boolean remove(Object o) { //移除对象 if (o == null) { //循环获得元素对象 for (MyEntry<Object> e = header.next; e != header; e = e.next) { if (e.object == null) { return this.remove(e); //移除对象 } } } else { for (MyEntry<Object> e = header.next; e != header; e = e.next) { //循环获得元素对象 if (o.equals(e.object)) { return this.remove(e); //移除对象 } } } return false; } public Object dequeue() { //出队方法 Object result = this.header.next.object; //获得对象 this.remove(this.header.next); //移除对象 return result; } public void enqueue(String o) { //入队方法 this.addBefore(o, header); //调方法添加对象 } public Object pop() { //出栈方法 Object result = this.header.previous.object;//获得对象 this.remove(this.header.previous); //移除对象 return result; } public void push(Object o) { //入栈 this.addBefore(o, header); //调方法添加对象 } private boolean remove(MyEntry e) { //移除对象 if (e == header) { return false; } e.previous.next = e.next; //重新赋值 e.next.previous = e.previous; //重新赋值 e.next = e.previous = null; //清空 e.object = null; this.size--; return true; } private MyEntry entry(int index) { //获得指定的对象 if (index < 0 || index >= this.size) { //判断指定元素的下标 return null; } MyEntry e = header; if (index < (this.size >> 1)) { //判断循环获得指定的实体 for (int i = 0; i <= index; i++) e = e.next; } else { for (int i = this.size; i > index; i--) e = e.previous; } return e; } } public class TextLinkedList { //操作LinkedList类实现栈和队列的类 public static void main(String[] args) { //Java程序主入口处 Queue myQueue = new LinkedList(); //实例化队列对象 myQueue.enqueue("HelloWorld"); //入队 myQueue.enqueue("Hello"); //入队 while (!myQueue.isEmpty()) { //循环判断队列是否为空 System.out.println(myQueue.dequeue()); //输出出队元素 } Stack myStack = new LinkedList(); //实例化栈对象 myStack.push("1234567890"); //入栈 myStack.push("0987654321"); //入栈 while (!myStack.isEmpty()) { //循环判断栈是否为空 System.out.println(myStack.pop()); //输出出栈元素 } } }
(3)运行结果如下所示:
HelloWorld Hello 0987654321 1234567890
源程序解读
(1)抽象类MyList中声明多个抽象方法,包括抽象向链表末尾中添加元素、抽象根据编号获得指定的元素、抽象判断元素的位置、抽象移除元素、抽象判断是否包含元素以及抽象清空链表元素等。
(2)接口Queue是队列接口,其声明入队与出队方法和判断队列是否为空。接口Stack是栈接口,其声明元素入栈与出栈以及判断栈是否为空。
(3)LinkedList继承MyList实现Queue接口和Stack接口,实现接口须实现接口中的全部方法,否则编译不通过。在类中创建一个静态内部类MyEntry,在内部类的构造方法中获得类对象以及上一个和下一个对象。创建MyEntry对象,在LinkedList类的构造方法中设置对象的上一个、下一个对象以及当前对象。
(4)addBefore()方法创建实体对象并设置对象的上一个和下一个对象为该实体对象。add()方法调用addBefore()方法并返回真值。clear()方法获得下一个对象,根据对象不是当前对象进行循环,获得这个对象的下一个对象,并将这个对象的下一个与上一个对象置空。contains()方法调用indexOf()方法判断对象是否存在,如果不存在则返回false。get()方法根据编号获得指定的对象,如果对象不为空则返回对象的元素。
(5)dequeue()方法获得对象的元素,调用remove()方法移除该对象并返回该对象。enqueue()方法调用addBefore()方法往对象中添加元素。pop()方法获得对象,调用remove()方法移除该对象并返回该对象。
实例67 电视频道(运用集的相关类)
Java为数据结构中的集合定义了一个接口java.util.Set,它有三个实现类,分别是HashSet、LinkedHashSet和TreeSet。本实例介绍如何使用这三个实现类以及这三个实现类之间的共性。
技术要点
使用集的相关类的技术要点如下:
• Set的特点:不允许元素重复,而且不维护元素的顺序。加入的元素必须定义equals()方法来确保对象惟一性。
• HashSet采用散列函数对元素进行排序,是专门为快速查询而设计的。存入HashSet的对象必须定义hashCode方法。
• TreeSet采用红黑树的数据结构进行排序元素,使用它可以从Set中提取有序(升序或者降序)的序列。需要注意的是,存入自定义类时,TreeSet需要维护元素的存储顺序,因此自定义类要实现Comparable接口并定义compareTo方法。
• LinkedHashSet内部使用散列以加快查询速度,同时使用链表维护元素的插入的次序,在使用迭代器遍历Set时,结果会按元素插入的次序显示。
实现步骤
(1)创建一个类名为TextSetClass.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 import java.util.ArrayList; //引入类 import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; public class TextSetClass { //操作使用Set的三个实现类的类 public static void initSet(Set<String> set) { //初始化Set的元素 if (set != null) { set.add("中央电视台"); set.add("湖南卫视"); set.add("新闻频道"); set.add("电影频道"); set.add("少儿节目"); } } public static void display(Set set) { //输出set的元素 if (set != null && set.size()>0) { Iterator it = set.iterator(); //获得迭代器Iterator while (it.hasNext()) { //循环获得Set每个元素 System.out.print(it.next() + " "); } }else{ System.out.println("没有元素!"); } System.out.println(); //换行 } public static void showHashSet() { //使用HashSet操作元素 Set hashSet = new HashSet(); initSet(hashSet); //调用方法初始化元素 System.out.println("使用HashSet操作元素: "); display(hashSet); //调用方法显示元素 } public static void showTreeSet() { //使用TreeSet操作元素 Set treeSet = new TreeSet(); initSet(treeSet); //调用方法初始化元素 System.out.println("使用TreeSet操作元素:"); display(treeSet); //调用方法显示元素 } public static void showLinkedHashSet() { //使用LinkedHashSet操作元素 Set linkedHashSet = new LinkedHashSet(); initSet(linkedHashSet); //调用方法初始化元素 System.out.println("使用LinkedHashSet操作元素:"); display(linkedHashSet); //调用方法显示元素 } public static void main(String[] args) { //Java程序主入口处 showHashSet(); showTreeSet(); showLinkedHashSet(); Set hashSet = new HashSet(); initSet(hashSet); hashSet.add("中央电视台"); //Set不允许元素重复 hashSet.add("少儿节目"); System.out.println("为hashSet加入中央电视台、少儿节目元素后: "); display(hashSet); //调用方法显示元素 hashSet.remove("中央电视台"); //删除元素 System.out.println("hashSet删除aaa元素后: "); display(hashSet); //调用方法显示元素 List list = new ArrayList(); //创建一个列表集合 list.add("少儿节目"); list.add("少儿节目"); list.add("中央电视台"); hashSet.addAll(list); //将列表集合添加到Set中 System.out.println("hashSet添加一个集合的所有元素后: "); display(hashSet); hashSet.retainAll(list); //删除除列表集合中的元素之外的元素 System.out.println("hashSet删除除了列表集合之外的元素后: "); display(hashSet); //调用方法显示元素 hashSet.removeAll(list); //删除集合中的元素 System.out.println("hashSet删除集合中的元素后: "); display(hashSet); //调用方法显示元素 //获取Set中元素的个数 System.out.println("hashSet中当前元素的个数: " + hashSet.size()); //判断Set中的元素是否为空 System.out.println("hashSet中当前元素为0? " + hashSet.isEmpty()); } }
(3)运行结果如下所示:
使用HashSet操作元素: 新闻频道 中央电视台 湖南卫视 少儿节目 电影频道 使用TreeSet操作元素: 中央电视台 少儿节目 新闻频道 湖南卫视 电影频道 使用LinkedHashSet操作元素: 中央电视台 湖南卫视 新闻频道 电影频道 少儿节目 为hashSet加入中央电视台、少儿节目元素后: 新闻频道 中央电视台 湖南卫视 少儿节目 电影频道 hashSet删除中央电视台元素后: 新闻频道 湖南卫视 少儿节目 电影频道 hashSet添加一个集合的所有元素后: 新闻频道 中央电视台 湖南卫视 少儿节目 电影频道 hashSet删除除了列表集合之外的元素后: 中央电视台 少儿节目 hashSet删除集合中的元素后: 没有元素! hashSet中当前元素的个数: 0 hashSet中当前元素为0? true
源程序解读
(1)initSet()方法初始化一个集(Set),通过add()方法往集中添加元素。
(2)display()方法遍历一个集(Set),并循环输出集的元素。集的iterator()方法获得迭代器Iterator,运用循环依次迭代输出集中的每个元素。
(3)showHashSet()方法、showTreeSet()方法和showLinkedSet()方法依次实现Set接口并调用方法进行初始化再将元素输出。
(4)在main()主方法中调用方法输出集(Set)不同实现类的元素,创建一个集(Set)的实现类hashSet并初始化,其运用remove()方法删除指定元素。创建一个列表集合并对其初始化,addAll()方法将列表集合中的元素全部添加到hashSet中,由于集(Set)不允许元素重复,在列表集合中的元素全部在hashSet中存在,则列表集合中的元素不能添加到hashSet中。retainAll(list)方法删除除list以外的hashSet中的全部元素,removeAll()方法删除hashSet中的全部元素。size()方法获得hashSet中元素的个数,isEmpty()方法判断hashSet中是否存在元素。
实例68 植物种类(运用映射的相关类)
Java为数据结构中的映射定义了一个接口java.util.Map,它有HashTable、HashMap、WeakHashMap、LinkedHashMap以及TreeMap等实现类。本实例介绍如何使用映射类HashTable、HashMap和TreeMap,并对各映射类进行比较。
技术要点
使用映射的相关类的技术要点如下:
• HashTable实现一个映射,所有的键值必须非空。为了能高效地工作,定义键的类必须实现hashcode()方法和equals()方法。HashTable支持线程的同步,即任一时刻只能有一个线程能写hashtable,因此导致HashTable在写入时会比较慢。
• HashMap是一个最常用的Map,它根据键的hashcode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为null;允许多条记录的值为null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap,可以会导致数据的不一致。如果需要同步,可用Collections的synchronizedMap方法使HashMap具有同步的能力。
• TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器。当用迭代器Iterator遍历TreeMap时,得到的记录是排过序的。
实现步骤
(1)新建一个类名为TextMapClass.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 import java.util.ArrayList; //引入类 import java.util.Collections; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.util.Hashtable; import java.util.TreeMap; public class TextMapClass { //操作HashMap、HashTable和TreeMap public static void showHashMap(){ //操作HashMap的方法 Map<String, String> hashMap=new HashMap<String, String>(); //HashMap是无序的 hashMap.put("001", "藻类植物"); hashMap.put("002", "菌类植物"); hashMap.put("003", "地衣类植物"); hashMap.put("004", "蕨类植物"); hashMap.put("005", "种子植物"); hashMap.put("006", null); hashMap.put(null, "苔藓植物"); Iterator iterator=hashMap.keySet().iterator(); //获得迭代器Iterator System.out.println("1.操作HashMap显示植物种类"); while (iterator.hasNext()) { //循环获得每个对象 Object key = iterator.next(); //获得每个键元素 System.out.print(hashMap.get(key)+"\t"); //输出值 } System.out.println(); } public static void showHashtable(){ //操作HashTable的方法 Hashtable<String, String> hashTable=new HashTable<String, String>(); //HashTable是无序的 hashTable.put("001", "藻类植物"); hashTable.put("002", "菌类植物"); hashTable.put("003", "地衣类植物"); hashTable.put("004", "蕨类植物"); hashTable.put("005", "种子植物"); Iterator iterator=hashTable.keySet().iterator() ;//获得迭代器Iterator System.out.println("2.操作HashTable显示植物种类"); while (iterator.hasNext()) { //循环获得每个对象 Object key = iterator.next(); //获得每个键元素 System.out.print(hashTable.get(key)+"\t");//输出值 } System.out.println(); } public static void showTreeMap(){ //操作TreeMap的方法 TreeMap<String, String> treeMap=new TreeMap<String, String>(); //TreeMap是无序的 treeMap.put("001", "藻类植物"); treeMap.put("002", "菌类植物"); treeMap.put("003", "地衣类植物"); treeMap.put("004", "蕨类植物"); treeMap.put("005", "种子植物"); treeMap.put("006", null); Iterator iterator=treeMap.keySet().iterator(); //获得迭代器Iterator System.out.println("3.操作TreeMap显示植物种类"); while (iterator.hasNext()) { //循环获得每个对象 Object key = iterator.next(); //获得每个键元素 System.out.print(treeMap.get(key)+"\t"); //输出值 } System.out.println(); } public static void showArrayList(){ //操作有序的ArrayList列表集合 ArrayList<String> arrayList = new ArrayList<String>(); //创建列表集合对象 arrayList.add("001藻类植物"); arrayList.add("003地衣类植物"); arrayList.add("006苔藓植物"); arrayList.add("004蕨类植物"); arrayList.add("005种子植物"); arrayList.add("002菌类植物"); System.out.println("4.排序前的植物种类"); for (int i = 0; i < arrayList.size(); i++) { //循环显示列表集合中的元素 System.out.print(arrayList.get(i)+"\t"); } System.out.println(); Collections.sort(arrayList); //对列表集合进行排序 System.out.println("5.ArrayList排序后植物种类"); for (int i = 0; i < arrayList.size(); i++) { //循环显示列表集合中的元素 System.out.print(arrayList.get(i)+"\t"); } System.out.println(); } public static void main(String[] args) { //Java程序主入口处 showHashMap(); //操作HashMap显示植物种类 showHashTable(); //操作HashTable显示植物种类 showTreeMap(); //操作TreeMap显示植物种类 showArrayList(); //操作ArrayList显示植物种类 } }
(3)运行结果如下所示:
1. 操作HashMap显示植物种类 苔藓植物 蕨类植物 种子植物 null 藻类植物 菌类植物 地衣类植物 2. 操作HashTable显示植物种类 种子植物 蕨类植物 地衣类植物 菌类植物 藻类植物 3. 操作TreeMap显示植物种类 藻类植物 菌类植物 地衣类植物 蕨类植物 种子植物 null 4. 排序前的植物种类 001藻类植物 003地衣类植物 006苔藓植物 004蕨类植物 005种子植物 002菌类植物 5. ArrayList排序后植物种类 001藻类植物 002菌类植物 003地衣类植物 004蕨类植物 005种子植物 006苔藓植物
源程序解读
(1)showHashMap()方法创建映射(Map)的实现类HashMap并对其进行初始化。Map的keySet()方法获得键的集合,再调用键集合的iterator()方法获得键的迭代器,依次迭代取出Map中的键,用get()方法获得键对应的植物的种类值,便完成了Map的遍历。HashMap是无序的则迭代出的值是无序排列,HashMap允许键值为空。
(2)showHashTable()方法创建映射(Map)的实现类HashTable并对其进行初始化。HashTable的keySet()方法获得键的集合,再调用键集合的iterator()方法获得键的迭代器,依次迭代取出HashTable中的键,用get()方法获得键对应的植物的种类值,便完成了HashTable的遍历。HashTable是无序的,运用迭代默认以倒序输出元素,不允许键值为空。
(3)showTreeMap()方法创建映射(Map)的实现类TreeMap并对其进行初始化。TreeMap的keySet()方法获得键的集合,再调用键集合的iterator()方法获得键的迭代器,依次迭代取出TreeMap中的键,用get()方法获得键对应的植物的种类值,便完成了TreeMap的遍历。当用迭代器遍历TreeMap时,输出的元素是排过序的。
(4)showArrayList()方法创建列表集合对象并对其进行初始化。列表集合元素是按放入的先后顺序输出。集合类的帮助类Collections的sort()方法对列表集合进行排序。默认以升序排列。
实例69 不重复的随机数序列
Java.util.Random类生成随机数序列,但不能保证序列中的元素不重复。本实例介绍生成不重复的随机数序列的两种方法:筛选法和排除法,能够在指定随机数范围的前提下,保证生成不重复的随机数序列。
技术要点
生成不重复的随机数序列的技术要点如下:
• 筛选法。将所有可能被生成的数字放到一个候选列表中,然后生成随机数k,作为下标,将候选列表中下标为k的数字放到结果列表中,同时,把它从候选列表中删除,继续生成下一个随机数作为下标,因为被选中过的数字都不在候选队列中了,所以,可以保证每次取得的随机数都是不重复的。
• 排除法。利用Random随机生成数字,如果新生成的数字在以前没有出现过,则放入结果队列,否则继续生成下一个随机数。
实现步骤
(1)新建一个类名为TextNotRepeatNum.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 import java.util.ArrayList; //引入类 import java.util.List; import java.util.Random; public class TextNotRepeatNum { //操作生成不重复随机数的序列的类 private int min; //最小数 private int max; //最大数 public TextNotRepeatNum(){ //构造方法初始化变量 this.min = 0; this.max = 10; } public TextNotRepeatNum(int min, int max){ //带参数构造方法初始化变量 this(); //调用上面的构造方法 if (max >= min){ this.min = min; this.max = max; } else { System.out.println("最大值小于最小值,按默认值进行赋值!"); } } public Integer[] getRandomByObviate(int length){//运用排除法生成不重复的随机数序列 if (length <= 0) { //判断传入的数值 return new Integer[0]; } else if (length > (this.max - this.min)) { System.out.println("长度不能达到:" + length + ", 长度只能是:" + (this.max - this.min)); length = this.max - this.min; //重新赋值 } Random random = new Random(); //用于生成随机对象 List result = new ArrayList(); //创建列表对象 while (result.size() < length) { //将[min, max]区间等价于min + [0, max - min + 1)生成随机数 Integer randnum = new Integer(this.min+random.nextInt(this.max-this.min +1)); if (!result.contains(randnum)){ //判断列表中是否包含对象 result.add(randnum); //添加整型对象 } } return (Integer[])result.toArray(new Integer[0]);//将列表转换成整型数组返回 } public Integer[] getRandomByFilter(int length) {//运用筛选法生成不重复的随机数序列 if (length <= 0) { return new Integer[0]; } else if (length > (this.max - this.min)) { System.out.println("长度不能达到:" + length + ", 长度只能是:" + (this.max - this.min)); length = this.max - this.min; } int numLength = this.max - this.min + 1; //初始化列表长度 List list = new ArrayList(); for (int i=this.min; i<= this.max; i++){ //循环依次获得整数 list.add(new Integer(i)); //在列表中添加整型数据 } Random rd = new Random(); //用于生成随机下标 List result = new ArrayList(); //创建列表对象 while (result.size() < length) { int index = rd.nextInt(numLength); //生成在[0,numLength)范围内的下标 result.add(list.get(index)); //下标为index数字对象放入列表对象中 list.remove(index); //移除下标为index的数字对象 numLength --; //候选队列长度减去1 } return (Integer[])result.toArray(new Integer[0]); //将列表转换成整型数组返回 } public static void showArray(Integer[] data){ //显示数组元素 if (data != null){ //判断数组是否为空 for (int i=0; i<data.length; i++){ //循环显示数组数据 System.out.print(data[i] + "\t"); } } System.out.println(); //换行 } public static void main(String []args){ //Java程序主入口处 TextNotRepeatNum test = new TextNotRepeatNum(3, 12); System.out.println("1.运用排除法生成不重复的随机数序列"); showArray(test.getRandomByObviate(6)); System.out.println("2.运用筛选法生成不重复的随机数序列"); showArray(test.getRandomByFilter(6)); } }
(3)运行结果如下所示:
1. 运用排除法生成不重复的随机数序列 3 5 10 12 4 11 2.运用筛选法生成不重复的随机数序列 9 12 3 5 7 4
源程序解读
(1)在本类中声明两个构造方法,允许用户指定随机数产生的范围,min变量表示随机数区间的最小值,max变量表示随机数区间的最大值,区间不一定要大于等于0开始,可以生成负数。
(2)getRandomByObviate()方法用排除法生成不重复的随机数序列。它将生成的随机数放在列表集合result中,如果新生成的随机数在列表集合result中,则继续生成,否则,运用其add()方法将新的随机数加入result,当result列表集合中的元素的个数不小于用户指定的随机数序列的长度时,将result列表集合运用toArray()方法转化成数组返回。
(3)getRandomByFilter()方法用筛选法生成不重复的随机数序列。它将用户指定的范围内的所有数都放入list列表集合中当作种子,假设list的长度是length,然后用Random在[0, length)的范围内生成随机数k,将list中下标为k的元素放入结果列表result中,并将它从list中删除,length的值减去1,继续在[0, length)范围内生成下一个随机数,当结果列表中元素的个数等于用户指定的随机数序列的长度时,将结果列表转化成数组返回。
(4)两种方法的比较:排除法需要利用Random生成很多个随机数,因为其中有些是重复的,所以不能放到结果列表中去,效率不是很高;筛选法能够保证每次生成的随机数都是新的,因为已经生成过的随机数已经在种子List中被删除了,但是它要求一开始就创建一个大的List当作种子,需要更多的空间花费。排除法适合在随机数范围比较大,而用户要求的随机数序列长度又比较短的情况下;筛选法适合在随机数范围比较小,且用户要求的随机数序列长度又比较长的情况下。
实例70 读写Properties文件
Properties配置文件是Java定义的一种全新的文件格式,以.properties为文件后缀。由于这种文件类型的灵活性与可维护性,因此被大量地用在Java程序的开发以及配置中。本实例介绍如何读取Properties文件的内容以及如何将内容写入Properties文件。
技术要点
读写Properties文件的技术要点如下:
• Properties是用来在一个文件中存储键—值对的,其中键和值是用等号分隔的。装载到Properties对象后,可以找到指定的键和对应的值。Properties类支持带“\u”的嵌入Unicode字符串,但重要的是每一项内容都当作字符串。
• 与Map相比,Properties能从输入流中获取键—值对信息,能将键—值对信息存入到输出流中。
实现步骤
(1)创建一个类名为TextReadOrWriteProp.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 import java.io.*; //引入类 import java.util.*; //操作Properties文件读写并支持各种字符集的类 public class TextReadOrWriteProp extends ArrayList { private static final long serialVersionUID = 1L; private String encoding = "GBK"; //设置编码方式 private String fileName; //文件名包括路径和后缀 public String getFileName() { //获得文件名字 return fileName; } private void setFileName(String fileName) { //设置文件名字 this.fileName = fileName; } //带参数的构造方法 public TextReadOrWriteProp(String fileName, String encoding) { try { this.setFileName(fileName); //设置文件 this.setCharacterEncoding(encoding); if (!isFileExist(fileName)) //判断文件是否存在 this.write(""); //调用方法设置编码方式 //调用方法将元素放入集合中 this.addAll(Arrays.asList(read(fileName, encoding).split("\n"))); } catch (Exception ex) { //捕获异常 ex.printStackTrace(); } } private void setCharacterEncoding(String encoding) throws UnsupportedEncodingException { //设置编码方式 new String("".getBytes("iso8859_1"), encoding); //编码转换 this.encoding = encoding; } public static boolean isFileExist(String fileName) { //判断文件是否存在 return new File(fileName).isFile(); //是否是一个文件 } public static String read(String fileName, String encoding) throws IOException { //读取信息 StringBuffer sb = new StringBuffer(); //创建字符缓冲流 BufferedReader in = new BufferedReader( //创建缓冲读对象 new FileReader(fileName)); String s; while ((s = in.readLine()) != null) { //循环读取文件中的信息 sb.append(s); //字符串拼接 sb.append("\n"); //换行 } in.close(); //释放资源 return sb.toString(); //返回读取的字符串 } public void write(String text) throws IOException { //字符串写入文件 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter( fileName))); //创建文本输出流打印对象 out.print(text); //将字符串写入指定文件 out.close(); //释放资源 } public void save() throws IOException { //数据保存到文件中 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter( fileName))); //创建文本输出流打印对象 String tmp; for (int i = 0; i < size(); i++) { //循环显示集合信息输出到控制台 tmp = get(i) + ""; out.println(tmp); } out.close(); } public void setProperties(String key, String val) { //设置Properties键值 int pro = findKey(key); if (pro >= 0) this.set(pro, key + "=" + val); else this.add(key + "=" + val); } public int findKey(String key) { //查找键序号 try { String temp; for (int i = 0; i < size(); i++) { //循环显示集合信息 temp = get(i) + ""; temp = new String(temp.getBytes("iso8859_1"), encoding);//编码转换 if (temp.indexOf(key) == 0) { //没有找到键值 return i; } } } catch (Exception e) { //捕获异常 } return -1; } public void setMemo(String key, String memo) { //增加备注 if ("".equals(key)) { this.add("#" + memo); return; } String temp; int result = findKey(key); if (result == -1) { //如果没有找到 this.add("#" + memo); this.add(key + "="); } else { int position = result - 1; if (position < 0){ this.add(position, "#" + memo); }else { temp = this.get(position) + " "; if ("#".equals(temp.substring(0, 1))) //判断截取值是否与#相同 this.set(position, "#" + memo); else{ this.add(position + 1, "#" + memo); } } } } public void setTitle(String title) { //设置注释内容 String tmp = this.get(0) + ""; if (tmp == null || tmp.length() == 0) tmp = ""; else tmp = tmp.substring(0, 1); //截取第一个元素 if ("#".equals(tmp)) //判断第一个元素是否是# this.set(0, "#" + title); //增加注释内容 else { this.add(0, ""); this.add(0, "#" + title); } } public String getProperties(String key) { //获取键对应的值 return getProperties(key, ""); } public String getProperties(String key, String defaultStr) { String temp, result; try { for (int i = 0; i < size(); i++) { //循环显示集合信息 temp = get(i) + ""; //获得元素 temp = new String(temp.getBytes("iso8859_1"), encoding);//编码转换 if (temp.indexOf(key) == 0) { //找到指定的键 result = temp.substring(key.length() + 1);//截取获得键对应的值 return result; } } } catch (Exception e) { //捕获异常 } return defaultStr; } public void getPropertiesFromClass() {//获得项目中的properties文件,根据键取得相应的值 try { InputStream in = new BufferedInputStream(new FileInputStream( "/pro.properties")); //创建输入流对象 Properties p = new Properties(); //创建属性对象 p.load(in); //加载输入流对象 String s = (String) p.get("username"); //获得键值 //编码转换 String username = new String(s.getBytes("iso8859_1"), "GBK"); System.out.println("输出username的值:"+username); } catch (IOException e) { //捕获异常 e.printStackTrace(); } } public static void main(String[] args) throws Exception {//Java程序主入口处 String path = "F:/pro.properties"; TextReadOrWriteProp text = new TextReadOrWriteProp(path, "GBK"); text.setTitle("测试读写Properties文件"); text.setProperties("username", "汤姆"); text.setMemo("username", "用户名"); //设置用户名备注 text.setProperties("password", "123456"); text.setMemo("password", "密码"); //设置密码备注 text.setProperties("address", "beijing"); text.setMemo("address", "用户地址"); //设置用户地址备注 text.save();//将内容写入属性文件 System.out.println(read(path, "GBK")); //读取属性文件内容 text.getPropertiesFromClass(); //读取项目中的属性文件的键值 } }
与TextReadOrWriteProp类同样的目录下的pro.properties:
#\ufffd\ufffd\ufffd\u0536\ufffd\u0434Properties\ufffd\u013c\ufffd #\ufffd\u00fb\ufffd\ufffd\ufffd username=\ufffd\ufffd\u0137 #\ufffd\ufffd\ufffd\ufffd password=123456 #\ufffd\u00fb\ufffd\ufffd\ufffd\u05b7 address=beijing
(3)运行结果如下所示:
#测试读写Properties文件 #用户名 username=汤姆 #密码 password=123456 #用户地址 address=beijing 输出username的值:汤姆
F:/pro.properties:
#\ufffd\ufffd\ufffd\u0536\ufffd\u0434Properties\ufffd\u013c\ufffd #\ufffd\u00fb\ufffd\ufffd\ufffd username=\ufffd\ufffd\u0137 #\ufffd\ufffd\ufffd\ufffd password=123456 #\ufffd\u00fb\ufffd\ufffd\ufffd\u05b7 address=beijing
源程序解读
(1)本程序中的类继承列表集合ArrayList,在构造方法中对文件和编码格式进行初始化,判断传入的文件是否存在,调用read()方法返回值,用“\n”分割成字符串数组,将数组转换成列表数组,运用addAll()方法添加到列表集合中。
(2)setCharacterEncoding()方法通过传入的参数设置编码方式,对encoding变量进行重新赋值。
(3)isFileExist()方法判断传入的参数是否能够获得文件。
(4)read()方法创建字符缓冲流,根据传入的文件创建缓冲读对象读取文件,运用循环逐行读取文件信息,再运用字符缓冲流将文件信息拼接起来,将拼接后的信息转成字符串返回。(5)write()方法创建文本输出流打印对象,运用print()方法将字符串数据保存到文件。
(6)save()方法创建文本输出流打印对象,循环列表集合数据将数据保存到文件中。
(7)findKey()方法循环将列表集合的数据进行编码转换,运用indexOf()方法判断是否存在键值。temp.indexOf(key)方法返回0表示key在temp中,返回-1表示在temp中没有找到key。
(8)setProperties()方法调用findKey()方法判断键是否存在,如果存在,则重新赋值;如果不存在则运用add()方法添加数据。
(9)setMemo()方法判断传入的键,为空则在备注前加“#”。调用findKey()方法判断键是否存在,如果不存在则添加带“#”的备注和键;如果存在并且存在带“#”的备注,则在备注下添加新备注。
(10)setTitle()方法判断列表集合第一个元素前是否存在“#”,如果存在则在备注下一行添加带“#”的备注;如果不存在则在元素前加带“#”的备注。
(11)getPropertiesFromClass()方法中根据属性文件创建输入流对象。创建属性对象运用load()方法加载输入流对象,属性对象的get("username")方法获得属性文件中键为username的值。
实例71 配置Properties带附件发送邮件
发送邮件需要配置一些参数,如邮件传输协议、收件人以及发件人地址、邮件主题和邮件内容等。本实例在Properties文件中配置邮件的参数,指定类获得参数发送带参数的附件。
技术要点
通过配置Properties文件发送带附件的邮件的技术要点如下:
• 不含附件的邮件只有一个部分:消息本身。带附件的电子通常至少由两部分组成:消息和附件。这样,带两个附件的邮件由三部分组成:消息、第一个附件和第二个附件。
• Properties类的put(key,value)方法设置配置文件的参数,在配置文件中以“key=value”形式存在。Session类的getDefaultInstance()方法创建会话,将会话添加到邮件对象中,对配置文件的参数进行处理。
实现步骤
(1)新建一个类名为TextPropertiesSendMail.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 import javax.mail.*; //引入类 import javax.mail.internet.*; import javax.activation.*; import java.util.*; public class TextPropertiesSendMail { //操作配置Properties文件发送带附件的邮件 private String host; //SMTP服务器地址 private String from; //发件人地址 private String to; //收件人地址 private String username; //发送人名称 private String password; //发送人密码 private String emailtitle; //邮件标题 private String text; //邮件内容 private List<FileDataSource> FileDataSources; //附件 private String files; //文件 public TextPropertiesSendMail() { //默认构造方法初始化对象 FileDataSources = new ArrayList<FileDataSource>(); } public boolean sendMail() throws Exception { //发送邮件方法 try { Properties props = new Properties(); //创建属性类 props.put("mail.smtp.host",getHost()); //设置SMTP服务器 props.put("mail.smtp.auth", "true"); //通过验证 props.put("mail.mime.charset", "GBK"); //设置编码格式 //根据属性文件创建会话 Session session = Session.getDefaultInstance(props); session.setDebug(true); //设置Debug MimeMessage message = new MimeMessage(session); //创建邮件 message.setFrom(new InternetAddress(getFrom())); //设置发件人地址 message.addRecipient(Message.RecipientType.TO, new InternetAddress (getTo())); //设置收件人地址 message.setSubject(getEmailtitle()); //设置邮件标题 int size = 0; size = this.FileDataSources.size(); //获得附件个数 if (this.FileDataSources != null && size > 0) { //判断是否有附件需要发送 MimeMultipart mm = new MimeMultipart(); //邮件载体 BodyPart bps = new MimeBodyPart(); //邮件内容体 //设置邮件内容的显示方式 bps.setContent(getText(), "text/html;charset=gb2312"); mm.addBodyPart(bps); //载体添加内容体 for (int i = 0; i < size; i++) { BodyPart bp = new MimeBodyPart(); DataHandler datahand = new DataHandler(this.FileDataSources .get(i)); bp.setDataHandler(datahand); //设置附件头信息 //设置附件名称 bp.setFileName(this.FileDataSources.get(i).getName()); mm.addBodyPart(bp); //载体添加附件 } message.setContent(mm); //将载体添加到邮件中 } else { //无附件直接添加邮件正文 message.setContent(getText(), "text/html;charset=gb2312"); } message.saveChanges(); //保存 Transport transport = session.getTransport("smtp"); //设置发送方式为smtp transport.connect(getHost(),getUsername(),getPassword()); //链接邮件主机 transport.sendMessage(message, message.getAllRecipients());//发送 transport.close(); //关闭链接 } catch (Exception e) { //捕获异常 System.out.println("发送邮件失败:" + e.getMessage()); System.out.println(0); return false; } return true; } public static void main(String[] args) throws Exception { //Java程序主入口处 TextPropertiesSendMail text = new TextPropertiesSendMail();//实例化对象 String SMTP = "smtp.sina.com"; //smtp服务器 String FROM = "aa@sina.com"; //发件人地址 String TO = "bb@qq.com"; //收件人地址 String USERNAME = "aaaaaa"; //发件人用户名 String PASSWORD = "888888"; //发件人密码 String TITLE = "A TEXT"; //邮件标题 String CONTENT = "测试发送带附件的文件"; //邮件内容 String DATASOURCES="F:\\123.rar,F:\\chuang.rar"; //附件 text.setScheme(SMTP, TO, FROM, USERNAME, PASSWORD); //调用方法设置参数 text.setCarrier(TITLE, CONTENT); //调方法设置标题和内容 text.setFileDataSources(DATASOURCES); //加入附件 text.sendMail(); //发送邮件 } public String getHost() { return host; } public void setHost(String aHost) { host = aHost; } public String getFrom() { return from; } public void setFrom(String aFrom) { from = aFrom; } public String getTo() { return to; } public void setTo(String aTo) { to = aTo; } public String getUsername() { return username; } public void setUsername(String aUsername) { username = aUsername; } public String getPassword() { return password; } public void setPassword(String aPassword) { password = aPassword; } public String getEmailtitle() { return emailtitle; } public void setEmailtitle(String aEmailtitle) { emailtitle = aEmailtitle; } public String getText() { return text; } public void setText(String aText) { text = aText; } public List<FileDataSource> getFileDataSources() { return FileDataSources; } public void setFileDataSources(String aFileDataSource) { String[] str = aFileDataSource.split(","); for (int i = 0; i < str.length; i++) { FileDataSource temp = new FileDataSource(str[i]); FileDataSources.add(temp); } } public String getFiles() { return files; } public void setFiles(String aFiles) { files = aFiles; } public void setScheme(String host, String to, String from, String username, String password) { this.host = host; this.to = to; this.from = from; this.username = username; this.password = password; } public void setCarrier(String title, String text) { this.emailtitle = title; this.text = text; } }
(3)运行结果如下所示:
DEBUG: setDebug: JavaMail version 1.4.2 DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp, com.sun.mail.smtp.SMTPTransport,Sun Microsystems, Inc] DEBUG SMTP: useEhlo true, useAuth true DEBUG SMTP: trying to connect to host "smtp.sina.com", port 25, isSSL false 220 irxd5-203.sinamail.sina.com.cn ESMTP DEBUG SMTP: connected to host "smtp.sina.com", port: 25 EHLO aaa 250-irxd5-203.sinamail.sina.com.cn 250-8BITMIME 250-SIZE 31457280 250-AUTH PLAIN LOGIN 250 AUTH=PLAIN LOGIN DEBUG SMTP: Found extension "8BITMIME", arg "" DEBUG SMTP: Found extension "SIZE", arg "31457280" DEBUG SMTP: Found extension "AUTH", arg "PLAIN LOGIN" DEBUG SMTP: Found extension "AUTH=PLAIN", arg "LOGIN" DEBUG SMTP: Attempt to authenticate DEBUG SMTP: check mechanisms: LOGIN PLAIN DIGEST-MD5 ......
源程序解读
(1)sendMail()方法创建Properties属性对象,并将与发送邮件相关的参数配置到属性对象中,根据会话的getDefaultInstance()方法封装属性对象创建会话。设置会话的Debug为true,可以在输出内容中看到DEBUG信息。根据会话创建邮件对象,并设置获取的发件人地址、收件人地址以及邮件标题。判断是否有附件需要发送,如果有附件,则创建邮件载体、邮件内容体并设置内容体的显示方式,将邮件内容体添加到邮件载体中;循环附件信息获得每个附件,创建附件数据柄对象并设置其附件头信息和附件名称,将附件数据柄添加到邮件载体中。最后将邮件载体添加到邮件对象中,如果没有附件,直接在邮件对象中添加邮件内容部分。
(2)通过saveChanges()方法将邮件对象保存,根据会话获得SMTP服务器传输对象,并链接传输对象的邮件主机。根据sendMessage()方法将邮件发送到指定邮箱。
实例72 资源国际化(Properties)
在许多网站中总有中英文切换功能,可以在中文环境下显示中文,在英文环境下显示英文。本实例介绍利用Java的国际化和经过Unicode编码的properties资源文件实现中英文切换,继而扩展实现多种语言的切换。
技术要点
利用Properties资源文件实现资源国际化的技术要点如下:
• Java中实现资源国际化的类是java.util.ResourceBundle,使用之前先绑定一个资源。如ResourceBundle.getBundle("*.resource"),*代表路径,它的含义是绑定当前classpath下面的*\resource.properties文件,以及所有的*\resource_**.properties文件。其中**代表任意的语言环境,如简体中文(zh_CN)、英文(en)等。
• 绑定之后运用ResourceBundle.getString(String key)方法获得properties属性文件,文件中键为key对应的值。如果当前语言环境为简体中文(zh_CN),程序会先在resource_zh_CN. properties中寻找键为key对应的值;如果没有找到该键(key)或属性文件resource_zh_ CN.properties不存在,它会到默认资源文件resource.properties中寻找。如果再没找到,会抛出MissingResourceException异常。
实现步骤
(1)创建一个类名为TextPropI18N.java。
(2)代码如下所示:
package com.zf.s8; //创建一个包 import java.util.Locale; //引入类 import java.util.ResourceBundle; public class TextPropI18N { //操作使用属性文件实现资源国际化的类 public static final String PROP_FILENAME = "com.zf.s8.resource"; public static final String POEM_KEY = "poem"; public static final String GLOBAL_KEY = "poem.global"; public static final String TEST = "test"; private static String poem; private static String global; private static String test; public static void showI18N() { //操作属性文件的方法 try { Locale locale = Locale.ENGLISH; //使用英文 ResourceBundle bundle = ResourceBundle.getBundle(PROP_FILENAME, locale); poem=bundle.getString(POEM_KEY).trim(); //获得键poem对应的值 global=bundle.getString(GLOBAL_KEY).trim(); //获得键global对应的值 test=bundle.getString(TEST); //获取只在resource.properties存在的键 System.out.println("poem使用默认值: "+poem); System.out.println("global使用默认值:"+global); System.out.println("test使用默认值:"+ test); locale=Locale.CHINESE; //使用简体中文 bundle = ResourceBundle.getBundle(PROP_FILENAME, locale); poem=bundle.getString(POEM_KEY).trim(); global=bundle.getString(GLOBAL_KEY).trim(); test=bundle.getString(TEST); System.out.println("poem使用默认值: "+poem); System.out.println("global使用默认值:"+global); System.out.println("test使用默认值:"+ test); } catch (Exception e) { System.err.println("不能加载属性文件"); poem="default poem"; global="default global"; test="default test"; } } public static void main(String[] args) { //Java程序主入口处 TextPropI18N text=new TextPropI18N(); //实例化对象 text.showI18N(); //调用方法显示信息 } }
resource.properties、resource_zh_CN.properties、resource_en.properties与TextPropI18N类同目录。其中resource.properties:
##poem poem=\u4f60\u770b\u4e0d\u89c1\u4f60\u81ea\u5df1\uff0c\u4f60\u6240\u770b\u89c1\ u7684\u53ea\u662f\u4f60\u7684\u5f71\u5b50. ##poem.global poem.global=\u6211\u4e0d\u80fd\u9009\u62e9\u90a3\u6700\u597d\u7684\u3002\u662f\ u90a3\u6700\u597d\u7684\u9009\u62e9\u6211\u3002. ##test test=\u8fd9\u662f\u4e00\u4e2a\u6d4b\u8bd5.
resource_zh_CN.properties:
##poem poem=\u4f60\u770b\u4e0d\u89c1\u4f60\u81ea\u5df1\uff0c\u4f60\u6240\u770b\u89c1\ u7684\u53ea\u662f\u4f60\u7684\u5f71\u5b50. ##poem.global poem.global=\u6211\u4e0d\u80fd\u9009\u62e9\u90a3\u6700\u597d\u7684\.\u662f\u90 a3\u6700\u597d\u7684\u9009\u62e9\u6211\.
resource_en.properties:
##poem poem=What you are you do not see, what you see is your shadow. ##poem.global poem.global=I cannot choose the best. The best chooses me.
(3)运行结果如下所示:
poem使用默认值: What you are you do not see, what you see is your shadow. global使用默认值: I cannot choose the best. The best chooses me. test使用默认值: 这是一个测试. poem使用默认值: 你看不见你自己,你所看见的只是你的影子. global使用默认值: 我不能选择那最好的,是那最好的选择我. test使用默认值: 这是一个测试.
源程序解读
showI18N()方法创建Locale对象,Locale类用于提供本地信息,通常称为语言环境。不同的语言、不同的国家和地区采用不同的Locale对象来表示。Locale.ENGLISH是获得英文环境。配置环境创建资源绑定类绑定到resource_en.properties资源文件。getString()方法获得资源文件中参数(键)对应的值。test键在resource_en.properties资源文件中没有找到,则默认寻找resource.properties资源文件中test键对应的值。Local.CHINESE是获得简体中文环境,配置环境创建资源绑定类绑定到resource_zh_CN.properties资源文件。test键在resource_zh_CN. properties资源文件中没有找到,则默认寻找resource.properties资源文件中test键对应的值。
常见问题 读取Properties文件出现中文乱码
在使用java.util.Properties类读取properties文件时中文是乱码,原因是java.util.Properties类的load()方法读取文件的内容时使用ISO-8859-1字符编码,也就是每个字节是一个Latin1字符。本节运用两种方式解决读取Properties文件出现的中文乱码。
技术要点
解决Properties文件出现中文乱码的技术要点如下:
(1)如果不涉及中文的问题,properties文件还是比较简单的,在properties文件中不能直接写入汉字,而是必须写汉字对应的Unicode编码,如“中文”对应的Unicode编码为\u4E2D\u6587。也就是说,在properties文件中如果要声明一个键—值对,不能写成key=中文,而必须写为Unicode格式:key=\u4E2D\u6587,否则编译不通过。Java提供native2ascii工具将中文转换为相应的Unicode编码,native2ascii位于JDK目录(安装目录)下的bin文件夹内。运行native2ascii程序,输入汉字按回车键,会出现相应的Unicode编码。由于单个汉字转换不利于维护,可以将整个文件里的中文一起转换成Unicode。语法格式:native2ascii -encoding UTF-8 F:/oldsource.properties F:/newsource.properties,其中-encoding指定源文件的编码方式,这里声明为UTF-8。oldsource.properties为源文件路径,newsource.properties为目的路径。
(2)有时运用native2ascii工具将properties文件内容转换为指定格式后,在读取文件内容时仍出现乱码问题。下面有两种解决的办法:
• 第一种解决办法:在线安装Poperties Editor。启动Eclipse,选择“帮助”→“软件更新”→“查找并安装”。选中“搜索要安装的新功能部件”,单击“下一步”按钮,出现更新要访问的站点的界面。单击“新建远程站点”按钮,在“新建更新站点”对话框的“名称”文本框中填写一个名字如propEdit,URL填写为http://propedit. sourceforge.jp/eclipse/updates,单击“确定”按钮。选中刚才新加的站点名称,单击“完成”按钮,Eclipse会自动找出与你所用的Eclipse版本相适应的插件。将其全部选中,只要单击“安装全部”按钮即可。最后会重启Eclipse,所有的properties文件名的前面都有一个绿色的p字。
• 第二种解决办法:把用native2ascii转换的properties文件另存为ANSI格式。