4. 常用类
本章将介绍若干常用的 Java 类。这些类拥有许多属性、方法和构造函数。每次我们仅介绍其中的一小部分。这些类的详细信息可在我们接下来介绍的 Java 帮助文档中查阅。
4.1. 文档
如果您已将 Sun 的 JDK 安装在 <jdk> 文件夹中,则文档位于 <jdk>\docs 文件夹内:

有时会遇到包含 jdk 文件但缺少文档的情况。相关文档可在 Sun 网站 http://www.sun.com 上找到。 在文件夹 docs 中,有一个名为 index.html 的文件,它是 JDK 帮助文档的起点:


上面的链接 API & Language 可访问 Java 类。链接 Demos/Tutorials 对于获取 Java 程序示例特别有用。让我们点击链接 API & Language:

点击Java 2 Platform链接API:

本页面是类文档的真正起点。可以为其创建快捷方式以便快速访问。URL位于<jdk>\docs\api\index.html。 该页面提供了 JDK 中数百个 Java 类的链接。初学者面临的主要困难在于了解这些不同类的作用。因此,最初阶段,只有当您知道想要查询信息的类名时,此帮助才具有参考价值。您也可以根据类名进行探索,因为类名通常能反映该类的作用。
让我们举个例子,查找关于实现动态数组的 Vector 类的信息。只需在左侧框架的类列表中查找 Vector 类的链接:

并点击该链接即可查看类定义:

其中包含
- 该类所属的类层次结构,此处为 java.util.Vector
- 该类的字段(属性)列表
- 构造函数列表
- 方法列表
接下来,我们将介绍各种类。建议读者系统地查阅所用类的完整定义。
4.2. 测试类
下文中的示例有时会使用类 personne 和 enseignant。在此重申其定义。
public class personne{
// 姓、名、年龄
private String prenom;
private String nom;
private int age;
// 构造函数 1
public personne(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
// 构造函数 2
public personne(personne P){
this.prenom=P.prenom;
this.nom=P.nom;
this.age=P.age;
}
// toString
public String toString(){
return "personne("+prenom+","+nom+","+age+")";
}
// 修饰词
public String getPrenom(){
return prenom;
}
public String getNom(){
return nom;
}
public int getAge(){
return age;
}
//修饰符
public void setPrenom(String P){
this.prenom=P;
}
public void setNom(String N){
this.nom=N;
}
public void setAge(int age){
this.age=age;
}
}
类 enseignant 派生自类 personne,定义如下:
class enseignant extends personne{
// 属性
private int section;
// 构造器
public enseignant(String P, String N, int age,int section){
super(P,N,age);
this.section=section;
}
// toString
public String toString(){
return "etudiant("+super.toString()+","+section+")";
}
}
我们还将使用一个从类 personne 派生并定义如下 的类 etudiant:
class etudiant extends personne{
String numero;
public etudiant(String P, String N, int age,String numero){
super(P,N,age);
this.numero=numero;
}
public String toString(){
return "etudiant("+super.toString()+","+numero+")";
}
}
4.3. String 类
String 类表示字符串。假设 nom 是一个字符串变量:
String 名称;
nom 是尚未初始化的对象引用。可以通过以下两种方式进行初始化:
nom="马" 或 nom=new String("马")
这两种方法效果相同。如果后续写入 nom="鱼",则 nom 将引用一个新对象。旧对象 String("cheval") 将被丢弃,其占用的内存空间将被回收。
String 类拥有丰富的属性和方法。以下列举其中部分:
public char charAt(int i) | 返回字符串中的第 i 个字符,其中第一个字符的索引为 0。因此 String("cheval").charAt(3) 等于 'v' |
public int compareTo(chaine2) | chaine1.compareTo(chaine2) 比较 chaine1 和 chaine2,若 chaine1=chaine2 则返回 0,若 chaine1>chaine2 则返回 1,若 chaine1<chaine2 则返回 -1 |
public boolean equals(Object anObject) | chaine1.equals(字符串2) 若字符串1=字符串2则返回true,否则返回false |
public String toLowerCase() | chaine1.toLowerCase() 将字符串1转换为小写 |
public String toUpperCase() | chaine1.toUpperCase() 将字符串1转换为大写 |
public String trim() | chaine1.trim() 去除字符串1首尾的空格 |
public String substring(int beginIndex, int endIndex) | String("chapeau").subString(2,4) 返回字符串 "ape" |
public char[] toCharArray() | 将字符串中的字符放入字符数组中 |
int length() | 字符串的字符数 |
int indexOf(String chaine2) | 返回当前字符串中 chaine2 的起始位置,若 chaine2 不存在则返回 -1 |
int indexOf(String chaine2, int startIndex) | 返回当前字符串中 chaine2 的第一个位置,若 chaine2 不存在则返回 -1。搜索从第 startIndex 个字符开始。 |
int lastIndexOf(String chaine2) | 返回当前字符串中 chaine2 的最后位置,若 chaine2 不存在则返回 -1 |
boolean startsWith(String 字符串2) | 如果当前字符串以 chaine2 开头,则返回 true |
boolean endsWith(String 字符串2) | 如果当前字符串以 chaine2 结尾,则返回 true |
boolean matches(String regex) | 如果当前字符串与正则表达式 regex 匹配,则返回 true。 |
String[] split(String regex) | 当前字符串由多个字段组成,这些字段由正则表达式 regex 定义的字符串分隔。方法 split 可将这些字段提取到数组中。 |
String replace(char oldChar, char newChar) | 将当前字符串中的字符 oldChar 替换为字符 newChar。 |
以下是一个示例程序:
// 导入
import java.io.*;
public class string1{
// 一个演示类
public static void main(String[] args){
String uneChaine="l'oiseau vole au-dessus des nuages";
affiche("uneChaine="+uneChaine);
affiche("uneChaine.Length="+uneChaine.length());
affiche("chaine[10]="+uneChaine.charAt(10));
affiche("uneChaine.IndexOf(\"vole\")="+uneChaine.indexOf("vole"));
affiche("uneChaine.IndexOf(\"x\")="+uneChaine.indexOf("x"));
affiche("uneChaine.LastIndexOf('a')="+uneChaine.lastIndexOf('a'));
affiche("uneChaine.LastIndexOf('x')="+uneChaine.lastIndexOf('x'));
affiche("uneChaine.substring(4,7)="+uneChaine.substring(4,7));
affiche("uneChaine.ToUpper()="+uneChaine.toUpperCase());
affiche("uneChaine.ToLower()="+uneChaine.toLowerCase());
affiche("uneChaine.Replace('a','A')="+uneChaine.replace('a','A'));
String[] champs=uneChaine.split("\\s+");
for (int i=0;i<champs.length;i++){
affiche("champs["+i+"]=["+champs[i]+"]");
}//用于
affiche("(\" abc \").trim()=["+" abc ".trim()+"]");
}//Main
// 显示
public static void affiche(String msg){
// 显示消息
System.out.println(msg);
}//显示
}//类
以及所得结果:
uneChaine=l'oiseau vole au-dessus des nuages
uneChaine.Length=34
chaine[10]=o
uneChaine.IndexOf("vole")=9
uneChaine.IndexOf("x")=-1
uneChaine.LastIndexOf('a')=30
uneChaine.LastIndexOf('x')=-1
uneChaine.substring(4,7)=sea
uneChaine.ToUpper()=L'OISEAU VOLE AU-DESSUS DES NUAGES
uneChaine.ToLower()=l'oiseau vole au-dessus des nuages
uneChaine.Replace('a','A')=l'oiseAu vole Au-dessus des nuAges
champs[0]=[l'oiseau]
champs[1]=[vole]
champs[2]=[au-dessus]
champs[3]=[des]
champs[4]=[nuages]
(" abc ").trim()=[abc]
4.4. Vector 类
向量是一个动态数组,其元素是对象的引用。因此,它是一个对象数组,其大小会随时间变化,而这是我们迄今为止遇到的静态数组所无法做到的。以下是该类的一些字段、构造函数或方法:
public Vector() | 创建一个空向量 |
public final int size() | 向量中的元素个数 |
public final void addElement(Object obj) | 将 obj 引用的对象添加到数组中 |
public final Object elementAt(int index) | 数组中编号为 index 的对象的引用——索引从 0 开始 |
public final Enumeration elements() | 以枚举形式返回数组中的所有元素 |
public final Object firstElement() | 向量中第一个元素的引用 |
public final Object lastElement() | 数组中最后一个元素的索引 |
public final boolean isEmpty() | 如果数组为空,则返回 true |
public final void removeElementAt(int index) | 移除索引为 index 的元素 |
public final void removeAllElements() | 清空数组中的所有元素 |
public final String toString() | 返回数组的标识字符串 |
以下是一个测试程序:
// 导入的类
import java.util.*;
public class test1{
// 主程序 main - static - 类方法
public static void main(String arg[]){
// 创建类实例对象
personne p=new personne("Jean","Dupont",30);
enseignant en=new enseignant("Paula","Hanson",56,27);
etudiant et=new etudiant("Chris","Garot",22,"19980405");
System.out.println("p="+p.toString());
System.out.println("en="+en.toString());
System.out.println("et="+et.toString());
// 多态性
personne p2=(personne)en;
System.out.println("p2="+p2.toString());
personne p3=(personne)et;
System.out.println("p3="+p3.toString());
// 向量
Vector V=new Vector();
V.addElement(p);V.addElement(en);V.addElement(et);
System.out.println("Taille du vecteur V = "+V.size());
for(int i=0;i<V.size();i++){
p2=(personne) V.elementAt(i);
System.out.println("V["+i+"]="+p2.toString());
}
} // main结束
}// 类结束
编译该程序:
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>dir
10/06/2002 10:41 1 134 personne.class
10/06/2002 10:41 619 enseignant.class
10/06/2002 10:41 610 etudiant.class
10/06/2002 10:42 1 035 test1.java
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>javac test1.java
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>dir
10/06/2002 10:41 1 134 personne.class
10/06/2002 10:41 619 enseignant.class
10/06/2002 10:41 610 etudiant.class
10/06/2002 10:42 1 035 test1.java
10/06/2002 10:43 1 506 test1.class
运行文件 test1.class:
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>java test1
p=personne(Jean,Dupont,30)
en=etudiant(personne(Paula,Hanson,56),27)
et=etudiant(personne(Chris,Garot,22),19980405)
p2=etudiant(personne(Paula,Hanson,56),27)
p3=etudiant(personne(Chris,Garot,22),19980405)
Taille du vecteur V = 3
V[0]=personne(Jean,Dupont,30)
V[1]=etudiant(personne(Paula,Hanson,56),27)
V[2]=etudiant(personne(Chris,Garot,22),19980405)
接下来,我们将不再重复测试程序的编译和运行过程。只需按照上述步骤操作即可。
4.5. 类 ArrayList
类 ArrayList 与类 Vector 类似。它们的主要区别仅在于当多个执行线程同时使用时。 访问 Vector 或 ArrayList 时,线程的同步方法有所不同。除该情况外,两者可互换使用。以下是该类的一些字段、构造函数或方法:
ArrayList() | 创建一个空数组 |
int size() | 数组中的元素数量 |
void add(Object obj) | 将由 obj 引用的对象添加到数组中 |
void add(int index, Object obj) | 将由 obj 引用的对象添加到数组的 index 位置 |
Object get(int index) | 数组中编号为 index 的对象引用 - 索引从 0 开始 |
boolean isEmpty() | 如果数组为空,则返回 true |
void remove(int index) | 移除索引为 index 的元素 |
void clear() | 清空数组中的所有元素 |
Object[] toArray() | 将动态数组转换为传统数组 |
String toString() | 返回数组的标识字符串 |
以下是一个测试程序:
// 导入的类
import java.util.*;
public class test1{
// 主程序 main - static - 类方法
public static void main(String arg[]){
// 创建类实例对象
personne p=new personne("Jean","Dupont",30);
enseignant en=new enseignant("Paula","Hanson",56,27);
etudiant et=new etudiant("Chris","Garot",22,"19980405");
System.out.println("p="+p);
System.out.println("en="+en);
System.out.println("et="+et);
// 多态性
personne p2=(personne)en;
System.out.println("p2="+p2);
personne p3=(personne)et;
System.out.println("p3="+p3);
// 向量
ArrayList personnes=new ArrayList();
personnes.add(p);personnes.add(en);personnes.add(et);
System.out.println("Nombre de personnes = "+personnes.size());
for(int i=0;i<personnes.size();i++){
p2=(personne) personnes.get(i);
System.out.println("personnes["+i+"]="+p2);
}
} // main结束
}// 类结束
所得结果与之前相同。
4.6. Arrays 类
java.util.Arrays 类提供了静态方法,用于对数组执行各种操作,特别是排序和元素搜索。以下是其中的一些方法:
static void sort(array) | 使用数组数据类型的隐含排序规则(数字或字符串)对 tableau 进行排序。 |
static void sort (Object[] 数组, Comparator C) | 对 tableau 进行排序,并使用比较函数 C 来比较元素 |
static int binarySearch(数组, 元素) | 返回 élément 在 tableau 中的位置,否则返回 <0。数组必须已排序。 |
static int binarySearch(Object[] 数组, Object 元素, Comparator C) | 同上,但使用比较函数 C 来比较数组中的两个元素。 |
以下是一个示例:
import java.util.*;
public class sort2 implements Comparator{
// 内部私有类
private class personne{
private String nom;
private int age;
public personne(String nom, int age){
this.nom=nom; // 人名
this.age=age; // 年龄
}
// 获取年龄
public int getAge(){
return age;
}
// 人员身份
public String toString(){
return ("["+nom+","+age+"]");
}
}; // 人员类
// 构造函数
public sort2() {
// 人员数组
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// 人员数组的排序
Arrays.sort(amis,this);
// 验证
for(int i=0;i<3;i++)
System.out.println(amis[i]);
}//构造函数
// 比较人员的功能
public int compare(Object o1, Object o2){
// 应返回
// -1,如果 o1 “小于” o2
// 若 o1 “等于” o2,则返回 0
// 若 o1 “大于” o2,则返回 +1
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//比较
// 测试函数
public static void main(String[] arg){
new sort2();
}//主
}//类别
让我们来分析一下这个程序。main函数创建了一个sort2对象。sort2类的构造函数如下:
// 建筑商
public sort2() {
// 人员表
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// 人员表的排序
Arrays.sort(amis,this);
// 验证
for(int i=0;i<3;i++)
System.out.println(amis[i]);
}//生成器
待排序的数组是一个 personne 对象数组。类 personne 在类 sort2 内部以私有方式定义(private)。 类 Arrays 的静态方法 sort 无法对 personne 对象数组进行排序,因此此处必须使用 void sort(Object[] obj, Comparator C) 这种形式。 Comparator 是一个仅定义了一个方法的接口:
且该方法必须返回 0:若 o1=o2, -1:若 o1 < o2,+1:若 o1 > o2。在原型 void sort(Object[] obj, Comparator C) 中,第二个参数 C 必须是实现接口 Comparator 的对象。 在构造函数 sort2 中,我们选择了当前对象 this:
这要求我们完成两项操作:
- 声明类 sort2 实现了接口 Comparator
- 在类 sort2 中编写函数 compare。
该函数如下:
// 比较人员的函数
public int compare(Object o1, Object o2){
// 应返回
// -1,如果 o1 “小于” o2
// 若 o1 "等于" o2,则返回 0
// 若 o1 “大于” o2,则返回 +1
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//比较
为了比较两个对象 personne,这里使用的是年龄(也可以使用姓名)。
执行结果如下:
我们本可以采用不同的方式来实现接口 Comparator:
import java.util.*;
public class sort2 {
// 一个内部私有类
private class personne{
…….
}; // Person类
// 构造函数
public sort2() {
// 一个人员数组
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// 对人员数组的排序
Arrays.sort(amis,
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//比较
}//类
);
// 验证
for(int i=0;i<3;i++)
System.out.println(amis[i]);
}//生成器
// 比较人员的功能
public int compare1(Object o1, Object o2){
// 应返回
// -1,如果 o1 “小于” o2
// 若 o1 “等于” o2,则返回 0
// 若 o1 “大于” o2,则返回 +1
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//比较1
// main
public static void main(String[] arg){
new sort2();
}//主程序
}//排序
排序指令变为如下:
// 人员数组排序
Arrays.sort(amis,
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//比较
}//类
);
方法 sort 的第二个参数必须是实现接口 Comparator 的对象。 在此,我们通过 new java.util.Comparator() 创建此类对象,而随后的文本 {….} 定义了要创建对象的类。 由于该类未命名,因此被称为匿名类。在这个必须实现 Comparator 接口的匿名类中,我们定义了该接口的 compare 方法。 该方法仅调用类 sort2 中的方法 compare1。因此,情况又回到了前一种情形。
类 sort2 不再实现接口 Comparator。因此,其声明变为:
现在,我们通过以下示例测试类 Arrays 中的方法 binarySearch:
import java.util.*;
public class sort4 {
// 一个内部私有类
private class personne{
// 属性
private String nom;
private int age;
// 构造函数
public personne(String nom, int age){
this.nom=nom; // 人名
this.age=age; // 年龄
}
// 获取姓名
public String getNom(){
return nom;
}
// 获取年龄
public int getAge(){
return age;
}
// 人员身份
public String toString(){
return ("["+nom+","+age+"]");
}
}; // 人员类别
// 构造函数
public sort4() {
// 人员数组
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// 比较器
java.util.Comparator comparateur1=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//比较
}//类
;
java.util.Comparator comparateur2=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare2(o1,o2);
}//比较
}//分类
;
// 人员表排序
Arrays.sort(amis,comparateur1);
// 验证
for(int i=0;i<3;i++)
System.out.println(amis[i]);
// 检索
cherche("milou",amis,comparateur2);
cherche("xx",amis,comparateur2);
}//生成器
// 人员比较函数
public int compare1(Object o1, Object o2){
// 应返回
// -1,如果 o1 “小于” o2
// 若 o1 "等于" o2,则返回 0
// 若 o1 “大于” o2,则返回 +1
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//compare1
// 将一个人与一个名字进行比较的函数
public int compare2(Object o1, Object o2){
// o1 是一个人
// o2 是一个字符串,即某人的名字 nom2
// 应返回
// -1,如果 o1.nom “小于” nom2
// 若 o1.nom “等于” nom2,则返回 0
// +1,如果 o1.nom “大于” nom2
personne p1=(personne)o1;
String nom1=p1.getNom();
String nom2=(String)o2;
return nom1.compareTo(nom2);
}//比较2
public void cherche(String ami,personne[] amis, Comparator comparateur){
// 在朋友表中查找朋友
int position=Arrays.binarySearch(amis,ami,comparateur);
// 找到?
if(position>=0)
System.out.println(ami + " a " + amis[position].getAge() + " ans");
else System.out.println(ami + " n'existe pas dans le tableau");
}//搜索
// 手
public static void main(String[] arg){
new sort4();
}//主
}//班级
这里,我们的做法与之前的示例略有不同。 方法 sort 和 binarySearch 所需的两个对象 Comparator 已创建,并分配给变量 comparateur1 和 comparateur2。
// 比较器
java.util.Comparator comparateur1=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//比较
}//类
;
java.util.Comparator comparateur2=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare2(o1,o2);
}//比较
}//分类
;
在sort4的生成器中,对表amis进行了两次二分搜索:
方法 cherche 接收了调用方法 binarySearch 所需的所有参数:
public void cherche(String ami,personne[] amis, Comparator comparateur){
// 在好友列表中搜索好友
int position=Arrays.binarySearch(amis,ami,comparateur);
// 找到?
if(position>=0)
System.out.println(ami + " a " + amis[position].getAge() + " ans");
else System.out.println(ami + " n'existe pas dans le tableau");
}//搜索
方法 binarySearch 与比较器 comparateur2 配合使用,而该比较器又调用了类 sort4 中的方法 compare2。 方法 rend 返回数组中搜索名称的位置(如果存在),否则返回一个小于 0 的数值。方法 compare2 用于将 personne 对象与 String 类型的名称进行比较。
// 将某人与姓名进行比对的功能
public int compare2(Object o1, Object o2){
// o1 是一个人
// o2 是一个字符串,即某人的名字 nom2
// 应返回
// -1 若 o1.nom “小于” nom2
// 0 若 o1.nom “等于” 名称2
// +1 若 o1.nom “大于” 名称2
personne p1=(personne)o1;
String nom1=p1.getNom();
String nom2=(String)o2;
return nom1.compareTo(nom2);
}//比较2
与方法 sort 不同, 方法 binarySearch 接收的不是两个 personne 对象,而是一个 personne 对象和一个 String 对象,且顺序必须如此。 第一个参数是数组 amis 中的一个元素,第二个参数是搜索对象的姓名。
4.7. Enumeration 类
Enumeration 是一个接口,而非类。它具有以下方法:
public abstract boolean hasMoreElements() | 如果枚举中还有元素,则返回 true |
public abstract Object nextElement() | 返回枚举中下一个元素的引用 |
如何使用枚举?通常如下所示:
以下是一个示例:
// 导入的类
import java.util.*;
public class test1{
// 主程序 main - static - 类方法
public static void main(String arg[]){
// 创建类实例对象
personne p=new personne("Jean","Dupont",30);
enseignant en=new enseignant("Paula","Hanson",56,27);
etudiant et=new etudiant("Chris","Garot",22,"19980405");
System.out.println("p="+p.toString());
System.out.println("en="+en.toString());
System.out.println("et="+et.toString());
// 多态性
personne p2=(personne)en;
System.out.println("p2="+p2.toString());
personne p3=(personne)et;
System.out.println("p3="+p3.toString());
// 向量
Vector V=new Vector();
V.addElement(p);V.addElement(en);V.addElement(et);
System.out.println("Taille du vecteur V = "+V.size());
int i;
for(i=0;i<V.size();i++){
p2=(personne) V.elementAt(i);
System.out.println("V["+i+"]="+p2.toString());
}
// 枚举
Enumeration E=V.elements();
i=0;
while(E.hasMoreElements()){
p2=(personne) E.nextElement();
System.out.println("V["+i+"]="+p2.toString());
i++;
}
}// main结束
}//类结束
得到以下结果:
p=personne(Jean,Dupont,30)
en=enseignant(personne(Paula,Hanson,56),27)
et=etudiant(personne(Chris,Garot,22),19980405)
p2=enseignant(personne(Paula,Hanson,56),27)
p3=etudiant(personne(Chris,Garot,22),19980405)
Taille du vecteur V = 3
V[0]=personne(Jean,Dupont,30)
V[1]=enseignant(personne(Paula,Hanson,56),27)
V[2]=etudiant(personne(Chris,Garot,22),19980405)
V[0]=personne(Jean,Dupont,30)
V[1]=enseignant(personne(Paula,Hanson,56),27)
V[2]=etudiant(personne(Chris,Garot,22),19980405)
4.8. Hashtable 类
Hashtable 类用于实现哈希表。可以将哈希表视为一个两列数组:
键 | 值 |
键1 | 值1 |
键2 | 值2 |
.. | ... |
键是唯一的,例如 c.a.d,因此不可能存在两个相同的键。Hashtable 类的主要方法和属性如下:
public Hashtable() | 构造函数——创建一个空的哈希表 |
public int size() | 字典中的元素数量——其中一个元素由 (键, 值) 组成 |
public Object put(Object key, Object value) | 将 (key, value) 对添加到哈希表中 |
public Object get(Object key) | 获取与键 key 关联的对象;若键 key 不存在,则返回 null |
public boolean containsKey(Object key) | 如果字典中存在键 key,则返回 true |
public boolean contains(Object value) | 如果字典中存在值 value,则返回 true |
public Enumeration keys() | 返回字典中键的枚举列表 |
public Object remove(Object key) | 移除键值为 key 的 (键, 值) 对 |
public String toString() | 识别该字典 |
以下是一个示例:
// 导入的类
import java.util.*;
public class test1{
// 主程序 main - static - 类方法
public static void main(String arg[]){
// 创建类实例对象
personne p=new personne("Jean","Dupont",30);
enseignant en=new enseignant("Paula","Hanson",56,27);
etudiant et=new etudiant("Chris","Garot",22,"19980405");
System.out.println("p="+p.toString());
System.out.println("en="+en.toString());
System.out.println("et="+et.toString());
// 多态
personne p2=(personne)en;
System.out.println("p2="+p2.toString());
personne p3=(personne)et;
System.out.println("p3="+p3.toString());
// 字典
Hashtable H=new Hashtable();
H.put("personne1",p);
H.put("personne2",en);
H.put("personne3",et);
Enumeration E=H.keys();
int i=0;
String cle;
while(E.hasMoreElements()){
cle=(String) E.nextElement();
p2=(personne) H.get(cle);
System.out.println("clé "+i+"="+cle+" valeur="+p2.toString());
i++;
}
}//main 结束
}//类结束
所得结果如下:
p=personne(Jean,Dupont,30)
en=enseignant(personne(Paula,Hanson,56),27)
et=etudiant(personne(Chris,Garot,22),19980405)
p2=enseignant(personne(Paula,Hanson,56),27)
p3=etudiant(personne(Chris,Garot,22),19980405)
clé 0=personne3 valeur=etudiant(personne(Chris,Garot,22),19980405)
clé 1=personne2 valeur=enseignant(personne(Paula,Hanson,56),27)
clé 2=personne1 valeur=personne(Jean,Dupont,30)
4.9. 文本文件
4.9.1. 写入
要向文件写入数据,必须拥有一个写入流。为此可以使用类 FileWriter。常用的构造函数如下:
FileWriter(String fileName) | 创建名为 fileName 的文件——随后可向其中写入内容——若存在同名文件将被覆盖 |
FileWriter(String fileName, boolean append) | 同上——若存在同名文件,可通过以追加模式打开该文件(append=true) |
FileWriter 类提供了一系列用于向文件写入数据的方法,这些方法继承自 Writer 类。若要向文本文件写入数据,建议使用 PrintWriter 类,其常用的构造函数如下:
PrintWriter(Writer out) | 参数类型为 Writer 或 c.a.d,表示一个写入流(文件、网络等) |
PrintWriter(Writer out, boolean autoflush) | 同上。第二个参数用于管理行缓冲。当其值为 false(默认值)时,写入文件的行会先经过内存缓冲区。当缓冲区已满时,内容才会写入文件。这有助于优化磁盘访问。不过,有时这种行为并不理想,特别是在网络写入时。 |
PrintWriter 类的常用方法如下:
void print(Type T) | 写入数据 T(字符串、整数等) |
void println(Type T) | 同上,末尾添加换行符 |
void flush() | 若未处于自动刷新模式,则清空缓冲区 |
void close() | 关闭写入流 |
以下是一个向文本文件写入几行的程序:
// 导入
import java.io.*;
public class ecrire{
public static void main(String[] arg){
// 打开文件
PrintWriter fic=null;
try{
fic=new PrintWriter(new FileWriter("out"));
} catch (Exception e){
Erreur(e,1);
}
// 写入文件
try{
fic.println("Jean,Dupont,27");
fic.println("Pauline,Garcia,24");
fic.println("Gilles,Dumond,56");
} catch (Exception e){
Erreur(e,3);
}
// 关闭文件
try{
fic.close();
} catch (Exception e){
Erreur(e,2);
}
}// main结束
private static void Erreur(Exception e, int code){
System.err.println("Erreur : "+e);
System.exit(code);
}//错误
}//类
运行后生成的文件 out 如下:
4.9.2. 读取
要读取文件内容,必须拥有与该文件关联的读取流。为此,可以使用类 FileReader 以及以下构造函数:
FileReader(String 文件名) | 从指定的文件打开一个读取流。如果操作失败,则抛出异常。 |
FileReader 类提供了一系列用于读取文件的方法,这些方法继承自 Reader 类。若要读取文本文件中的文本行,建议使用 BufferedReader 类并调用以下构造函数:
BufferedReader(Reader in) | 该构造函数从 in 类型的输入流中打开一个缓冲读取流。这种 Reader 类型的输入流可以来自键盘、文件、网络等。 |
BufferedReader 类的常用方法如下:
int read() | 读取一个字符 |
String readLine() | 读取一行文本 |
int read(char[] buffer, int offset, int size) | 从文件中读取 taille 个字符,并将其放入数组 buffer 中,从位置 offset 开始。 |
void close() | 关闭读取流 |
以下是一个读取先前创建的文件内容的程序:
// 导入的类
import java.util.*;
import java.io.*;
public class lire{
public static void main(String[] arg){
personne p=null;
// 打开文件
BufferedReader IN=null;
try{
IN=new BufferedReader(new FileReader("out"));
} catch (Exception e){
Erreur(e,1);
}
// 数据
String ligne=null;
String[] champs=null;
String prenom=null;
String nom=null;
int age=0;
// 处理可能出现的错误
try{
while((ligne=IN.readLine())!=null){
champs=ligne.split(",");
prenom=champs[0];
nom=champs[1];
age=Integer.parseInt(champs[2]);
System.out.println(""+new personne(prenom,nom,age));
}// while 结束
} catch (Exception e){
Erreur(e,2);
}
// 关闭文件
try{
IN.close();
} catch (Exception e){
Erreur(e,3);
}
}// main 结束
// 错误
public static void Erreur(Exception e, int code){
System.err.println("Erreur : "+e);
System.exit(code);
}
}// 类结束
运行该程序得到以下结果:
4.9.3. 保存人员对象
我们将刚才所学的内容应用到类 personne 中,为其提供一个方法,用于将人员的属性保存到文件中。我们在类 personne 的定义中添加方法 sauveAttributs:
// ------------------------------
// 保存到文本文件
// ------------------------------
public void sauveAttributs(PrintWriter P){
P.println(""+this);
}
在定义类 personne 之前,请务必导入包 java.io:
方法 sauveAttributs 接收的唯一参数是流 PrintWriter,该方法需向其中写入数据。一个测试程序可以如下所示:
// 导入
import java.io.*;
// 导入person;
public class sauver{
public static void main(String[] arg){
// 打开文件
PrintWriter fic=null;
try{
fic=new PrintWriter(new FileWriter("out"));
} catch (Exception e){
Erreur(e,1);
}
// 写入文件
try{
new personne("Jean","Dupont",27).sauveAttributs(fic);
new personne("Pauline","Garcia",24).sauveAttributs(fic);
new personne("Gilles","Dumond",56).sauveAttributs(fic);
} catch (Exception e){
Erreur(e,3);
}
// 关闭文件
try{
fic.close();
} catch (Exception e){
Erreur(e,2);
}
}// main结束
// 错误
private static void Erreur(Exception e, int code){
System.err.println("Erreur : "+e);
System.exit(code);
}//错误
}//类
编译并运行该程序:
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>javac sauver.java
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>dir
10/06/2002 10:52 1 352 personne.class
10/06/2002 10:53 842 sauver.java
10/06/2002 10:53 1 258 sauver.class
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>java sauver
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>dir
10/06/2002 10:52 1 352 personne.class
10/06/2002 10:53 842 sauver.java
10/06/2002 10:53 1 258 sauver.class
10/06/2002 10:53 83 out
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>more out
personne(Jean,Dupont,27)
personne(Pauline,Garcia,24)
personne(Gilles,Dumond,56)
4.10. 二进制文件
4.10.1. RandomAccessFile 类
RandomAccessFile 类用于管理二进制文件,特别是像 C/C++ 语言中常见的固定结构文件。以下是一些有用的方法和构造函数:
RandomAccessFile(String 文件名, String 模式) | 构造函数——以指定的模式打开指定文件。模式取值如下: r:仅读取模式 rw:读写模式 |
void writeTTT(TTT 值) | 将值写入文件。TTT 代表值的类型。值的内存表示形式将原样写入文件。因此会看到 writeBoolean、writeByte、writeInt、 writeDouble、writeLong、writeFloat... 等。若要写入字符串,则使用 writeBytes(String 字符串)。 |
TTT readTTT() | 读取并返回 TTT 类型的值。因此有 readBoolean、readByte、readInt、 readDouble、readLong、readFloat……方法 read() 读取一个字节。 |
long length() | 文件大小(以字节为单位) |
long getFilePointer() | 文件指针的当前位置 |
void seek(long pos) | 将文件指针定位到第 pos 个字节处 |
4.10.2. article 类
以下所有示例都将使用以下 article 类:
// 文章结构
private static class article{
// 定义结构
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//物料类
上面的 Java 类 article 将等同于以下 C 语言结构体 article
因此,我们将代码限制为4个字符,名称限制为20个字符。
4.10.3. 写入一条记录
以下程序将一条记录写入名为“data”的文件中:
// 导入类
import java.io.*;
public class test1{
// 测试将结构(C语言意义上的)写入二进制文件
// 文章结构
private static class article{
// 定义结构
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//文章类
public static void main(String arg[]){
// 定义用于存储文章的二进制文件
RandomAccessFile fic=null;
// 定义一个文章
article art=new article();
art.code="a100";
art.nom="velo";
art.prix=1000.80;
art.stockActuel=100;
art.stockMinimum=10;
// 定义文件
try{
fic=new RandomAccessFile("data","rw");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data",1);
}//try-catch
// 写入
try{
ecrire(fic,art);
} catch (IOException E){
erreur("Erreur lors de l'écriture de l'enregistrement",2);
}//try-catch
// 完成
try{
fic.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data",2);
}//try-catch
}//main
// 编写方法
public static void ecrire(RandomAccessFile fic, article art) throws IOException{
// 代码
fic.writeBytes(art.code);
// 名称限制为20个字符
art.nom=art.nom.trim();
int l=art.nom.length();
int nbBlancs=20-l;
if(nbBlancs>0){
String blancs="";
for(int i=0;i<nbBlancs;i++) blancs+=" ";
art.nom+=blancs;
} else art.nom=art.nom.substring(0,20);
fic.writeBytes(art.nom);
// 价格
fic.writeDouble(art.prix);
// 库存
fic.writeInt(art.stockActuel);
fic.writeInt(art.stockMinimum);
}// 写入结束
// ------------------------错误
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// 结束错误
}// 类结束
通过以下程序,我们可以验证执行是否正确完成。
4.10.4. 读取一条记录
// 导入的类
import java.io.*;
public class test2{
// 测试将结构(C语言意义上的)写入二进制文件
// article 结构
private static class article{
// 定义结构
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//article类
public static void main(String arg[]){
// 定义用于存储文章的二进制文件
RandomAccessFile fic=null;
// 以读取模式打开文件
try{
fic=new RandomAccessFile("data","r");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data",1);
}//try-catch
// 读取文件中的唯一文章
article art=new article();
try{
lire(fic,art);
} catch (IOException E){
erreur("Erreur lors de la lecture de l'enregistrement",2);
}//try-catch
// 显示读取到的记录
affiche(art);
// 完成
try{
fic.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data",2);
}//try-catch
}// 手动结束
// 读取方法
public static void lire(RandomAccessFile fic, article art) throws IOException{
// 代码读取
art.code="";
for(int i=0;i<4;i++) art.code+=(char)fic.readByte();
// 名称
art.nom="";
for(int i=0;i<20;i++) art.nom+=(char)fic.readByte();
art.nom=art.nom.trim();
// 价格
art.prix=fic.readDouble();
// 库存
art.stockActuel=fic.readInt();
art.stockMinimum=fic.readInt();
}// 写入结束
// ---------------------显示
public static void affiche(article art){
System.out.println("code : "+art.code);
System.out.println("nom : "+art.nom);
System.out.println("prix : "+art.prix);
System.out.println("Stock actuel : "+art.stockActuel);
System.out.println("Stock minimum : "+art.stockMinimum);
}// 结束显示
// ------------------------错误
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// 结束错误
}// 类结束
执行结果如下:
E:\data\serge\JAVA\random>java test2
code : a100
nom : velo
prix : 1000.8
Stock actuel : 100
Stock minimum : 10
成功读取了由写入程序写入的记录。
4.10.5. 文本转二进制
以下程序是对记录写入程序的扩展。现在我们将多个记录写入名为 data.bin 的二进制文件中。数据取自以下文件 data.txt:
E:\data\serge\JAVA\random>more data.txt
a100:velo:1000:100:10
b100:pompe:65:6:2
c100:arc:867:10:5
d100:fleches - lot de 6:450:12:8
e100:jouet:10:2:3
// 导入的类
import java.io.*;
import java.util.*;
public class test3{
// 文本文件 --> 二进制文件
// 文章结构
private static class article{
// 定义结构
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//商品类
public static void main(String arg[]){
// 定义用于存储文章的二进制文件
RandomAccessFile dataBin=null;
try{
dataBin=new RandomAccessFile("data.bin","rw");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data.bin",1);
}
// 数据从文本文件中提取
BufferedReader dataTxt=null;
try{
dataTxt=new BufferedReader(new FileReader("data.txt"));
} catch (IOException E){
erreur("Impossible d'ouvrir le fichier data.txt",2);
}
// .txt 文件 --> .bin 文件
String ligne=null;
String[] champs=null;
int numLigne=0;
String champ=null;
article art=new article(); // 待创建的商品
try{
while((ligne=dataTxt.readLine())!=null){
// 一行 +
numLigne++;
// 拆分为字段
champs=ligne.split(":");
// 需要 5 个字段
if(champs.length!=5)
erreur("Ligne "+numLigne+" erronée dans data.txt",3);
//代码
art.code=champs[0];
if(art.code.length()!=4)
erreur("Code erroné en ligne "+numLigne+" du fichier data.txt",12);
// 姓、名
art.nom=champs[1];
// 价格
try{
art.prix=Double.parseDouble(champs[2]);
} catch (Exception E){
erreur("Prix erroné en ligne "+numLigne+" du fichier data.txt",4);
}
// 当前库存
try{
art.stockActuel=Integer.parseInt(champs[3]);
} catch (Exception E){
erreur("Stock actuel erroné en ligne "+ numLigne + " du fichier data.txt",5);
}
// 当前库存
try{
art.stockActuel=Integer.parseInt(champs[3]);
} catch (Exception E){
erreur("Stock actuel erroné en ligne "+ numLigne + " du fichier data.txt",5);
}
// 写入记录
try{
ecrire(dataBin,art);
} catch (IOException E){
erreur("Erreur lors de l'écriture de l'enregistrement "+numLigne,7);
}
// 跳转到下一行
}// while 结束
} catch (IOException E){
erreur("Erreur lors de la lecture du fichier data.txt après la ligne "+numLigne,8);
}
// 结束
try{
dataBin.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.bin",10);
}
try{
dataTxt.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.txt",11);
}
}// main结束
// 写入方法
public static void ecrire(RandomAccessFile fic, article art) throws IOException{
// 代码
fic.writeBytes(art.code);
// 名称限制为20个字符
art.nom=art.nom.trim();
int l=art.nom.length();
int nbBlancs=20-l;
if(nbBlancs>0){
String blancs="";
for(int i=0;i<nbBlancs;i++) blancs+=" ";
art.nom+=blancs;
} else art.nom=art.nom.substring(0,20);
fic.writeBytes(art.nom);
// 价格
fic.writeDouble(art.prix);
// 库存
fic.writeInt(art.stockActuel);
fic.writeInt(art.stockMinimum);
}// 写入结束
// ------------------------错误
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// 结束错误
}// 类结束
以下程序可用于验证该程序是否运行正常。
4.10.6. 二进制转文本
以下程序读取先前创建的二进制文件 data.bin 的内容,并将内容写入文本文件 data.text。 如果一切正常,文件 data.text 应与原始文件 data.txt 完全一致。
// 导入的类
import java.io.*;
import java.util.*;
public class test5{
// 文本文件 --> 二进制文件
// 文章结构
private static class article{
// 定义结构
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//商品类
// 主函数
public static void main(String arg[]){
// 定义用于存储文章的二进制文件
RandomAccessFile dataBin=null;
try{
dataBin=new RandomAccessFile("data.bin","r");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data.bin en lecture",1);
}
// 数据写入文本文件
PrintWriter dataTxt=null;
try{
dataTxt=new PrintWriter(new FileWriter("data.text"));
} catch (IOException E){
erreur("Impossible d'ouvrir le fichier data.text en écriture",2);
}
// .bin 文件 --> .text 文件
article art=new article(); // 待创建的商品
// 读取二进制文件
int numRecord=0;
long l=0; // 文件大小
try{
l=dataBin.length();
} catch (IOException e){
erreur("Erreur lors du calcul de la longueur du fichier data.bin",2);
}
long pos=0; // 文件中的当前位置
try{
pos=dataBin.getFilePointer();
} catch (IOException e){
erreur("Erreur lors de la lecture de la position courante dans data.bin",2);
}
// 只要未超过文件末尾
while(pos<l){
// 读取当前记录并处理
numRecord++;
try{
lire(dataBin,art);
} catch (Exception e){
erreur("Erreur lors de la lecture de l'enregistrement "+numRecord,2);
}
affiche(art);
// 将相应的文本行写入 dataTxt
dataTxt.println(art.code.trim()+":"+art.nom.trim()+":"+art.prix+":"+art.stockActuel+":"+art.stockMinimum);
// 继续吗?
try{
pos=dataBin.getFilePointer();
} catch (IOException e){
erreur("Erreur lors de la lecture de la position courante dans data.bin",2);
}
}// while 结束
// 完成
try{
dataBin.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.bin",2);
}
try{
dataTxt.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.text",2);
}
}// main结束
// 读取方法
public static void lire(RandomAccessFile fic, article art) throws IOException{
// 读取代码
art.code="";
for(int i=0;i<4;i++) art.code+=(char)fic.readByte();
// 名称
art.nom="";
for(int i=0;i<20;i++) art.nom+=(char)fic.readByte();
art.nom=art.nom.trim();
// 价格
art.prix=fic.readDouble();
// 库存
art.stockActuel=fic.readInt();
art.stockMinimum=fic.readInt();
}// 写入结束
// ---------------------显示
public static void affiche(article art){
System.out.println("code : "+art.code);
System.out.println("nom : "+art.nom);
System.out.println("prix : "+art.prix);
System.out.println("Stock actuel : "+art.stockActuel);
System.out.println("Stock minimum : "+art.stockMinimum);
}// 结束显示
// ------------------------错误
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// 错误结束
}// 类结束
以下是一个执行示例:
E:\data\serge\JAVA\random>java test5
code : a100
nom : velo
prix : 1000.0
Stock actuel : 100
Stock minimum : 0
code : b100
nom : pompe
prix : 65.0
Stock actuel : 6
Stock minimum : 0
code : c100
nom : arc
prix : 867.0
Stock actuel : 10
Stock minimum : 0
code : d100
nom : fleches - lot de 6
prix : 450.0
Stock actuel : 12
Stock minimum : 0
code : e100
nom : jouet
prix : 10.0
Stock actuel : 2
Stock minimum : 0
E:\data\serge\JAVA\random>more data.text
a100:velo:1000.0:100:0
b100:pompe:65.0:6:0
c100:arc:867.0:10:0
d100:fleches - lot de 6:450.0:12:0
e100:jouet:10.0:2:0
4.10.7. 直接访问记录
该程序演示了直接访问二进制文件中记录的功能。它显示文件 data.bin 中指定编号的记录(该编号作为参数传入),其中第 1 条记录的编号为 1。
// 导入的类
import java.io.*;
import java.util.*;
public class test6{
// 文本文件 --> 二进制文件
// 文章结构
private static class article{
// 定义结构
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//文章类
// main
public static void main(String[] args){
// 验证参数
int nbArguments=args.length;
String syntaxe="syntaxe : pg numéro_de_fiche";
if(nbArguments!=1)
erreur(syntaxe,20);
// 验证主文件编号
int numRecord=0;
try{
numRecord=Integer.parseInt(args[0]);
} catch(Exception e){
erreur(syntaxe+"\nNuméro de fiche incorrect",21);
}
// 以只读模式打开二进制文件
RandomAccessFile dataBin=null;
try{
dataBin=new RandomAccessFile("data.bin","r");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data.bin en lecture",1);
}
// 定位到目标记录
try{
dataBin.seek((numRecord-1)*40);
} catch (Exception e){
erreur("La fiche "+numRecord+" n'existe pas",23);
}
// 读取该记录
article art=new article();
try{
lire(dataBin,art);
} catch (Exception e){
erreur("Erreur lors de la lecture de l'enregistrement "+numRecord,2);
}
// 显示该记录
affiche(art);
// 完成
try{
dataBin.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.bin",2);
}//try-catch
}// main结束
// 读取方法
public static void lire(RandomAccessFile fic, article art) throws IOException{
// 读取代码
art.code="";
for(int i=0;i<4;i++) art.code+=(char)fic.readByte();
// 名称
art.nom="";
for(int i=0;i<20;i++) art.nom+=(char)fic.readByte();
art.nom=art.nom.trim();
// 价格
art.prix=fic.readDouble();
// 库存
art.stockActuel=fic.readInt();
art.stockMinimum=fic.readInt();
}// 写入结束
// ---------------------显示
public static void affiche(article art){
System.out.println("code : "+art.code);
System.out.println("nom : "+art.nom);
System.out.println("prix : "+art.prix);
System.out.println("Stock actuel : "+art.stockActuel);
System.out.println("Stock minimum : "+art.stockMinimum);
}// 结束显示
// ------------------------错误
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// 结束错误
}// 类结束
以下是执行示例:
E:\data\serge\JAVA\random>java test6 2
code : b100
nom : pompe
prix : 65.0
Stock actuel : 6
Stock minimum : 0
E:\data\serge\JAVA\random>java.bat test6 20
Erreur lors de la lecture de l'enregistrement 20
4.11. 使用正则表达式
4.11.1. java.util.regex 包
java.util.regex 包支持使用正则表达式。正则表达式可用于验证字符串的格式。例如,可以验证表示日期的字符串是否确实采用 dd/mm/yy 格式。为此,需要使用一个模式并将字符串与该模式进行比较。因此,在此示例中,j、m 和 a 必须是数字。 因此,有效的日期格式模板为“\d\d/\d\d/\d\d”,其中符号 \d 表示数字。模板中可使用的符号如下(微软文档):
字符 | 描述 |
\ | 将下一个字符标记为特殊字符或字面字符。例如,"n" 表示字符 "n"。"\n" 表示换行符。字符序列 "\\" 表示 "\",而 "\(" 表示 "("。 |
^ | 表示输入的开头。 |
$ | 表示输入的结尾。 |
* | 表示前一个字符零次或多次出现。因此,“zo*”匹配“z”或“zoo”。 |
+ | 匹配前一个字符一次或多次。因此,“zo+”匹配“zoo”,但不匹配“z”。 |
? | 匹配前一个字符零次或一次。例如,“a?ve?”匹配“lever”中的“ve”。 |
. | 匹配除换行符以外的任何单个字符。 |
(模板) | 搜索 modèle 并记录匹配结果。 可以通过 Item [0]...[n] 从获得的 Matches 集合中提取匹配的子字符串。要查找包含括号 ( ) 内的字符的匹配项,请使用 "\(" 或 "\)"。 |
x|y | 匹配 x 或 y。例如,“z|foot”匹配“z”或“foot”。“(z|f)oo”匹配“zoo”或“foo”。 |
{n} | n 是一个非负整数。它精确匹配 n 倍的该字符。例如,“o{2}”不匹配“Bob,”中的“o”,而是匹配“fooooot”中的前两个“o”。 |
{n,} | n 是一个非负整数。匹配至少 n 倍的该字符。 例如,“o{2,}”不匹配“Bob”中的“o”,而是匹配“fooooot”中的所有“o”。“o{1,}”等同于“o+”,而“o{0,}”等同于“o*”。 |
{n,m} | m 和 n 是非负整数。 表示该字符出现次数不少于 n 次,且不超过 m 次。例如,“o{1,3}”匹配“foooooot”中的前三个“o”,而“o{0,1}”等同于“o?”。 |
[xyz] | 字符集。匹配所列字符中的任意一个。例如,“[abc]”匹配“plat”中的“a”。 |
[^xyz] | 否定字符集。匹配所有未列出的字符。例如,“[^abc]”匹配“plat”中的“p”。 |
[a-z] | 字符范围。匹配指定字符序列中的任何字符。例如,“[a-z]”匹配“a”到“z”之间的所有小写字母。 |
[^m-z] | 负字符集。匹配指定字符集中的所有非字符。例如,“[^m-z]”匹配“m”和“z”之间的所有非字符。 |
\b | 匹配表示单词的边界,即单词与空格之间的位置。例如,“er\b”匹配“lever”中的“er”,但不匹配“verbe”中的“er”。 |
\B | 匹配不代表单词的边界。“en*t\B”匹配“bien entendu”中的“ent”。 |
\d | 表示一个数字字符。等同于 [0-9]。 |
\D | 匹配不代表数字的字符。等同于 [^0-9]。 |
\f | 对应于换行符。 |
\n | 表示换行符。 |
\r | 对应回车符。 |
\s | 表示任何空白字符,包括空格、制表符、分页符等。等同于“[ \f\n\r\t\v]”。 |
\S | 匹配任何非空白字符。等同于“[^ \f\n\r\t\v]”。 |
\t | 匹配制表符。 |
\v | 匹配垂直制表符。 |
\w | 匹配任何代表单词的字符,包括下划线。等同于“[A-Za-z0-9_]”。 |
\W | 匹配任何不代表单词的字符。等同于“[^A-Za-z0-9_]”。 |
\num | 对应于 num,其中 num 是一个正整数。指代存储的映射关系。例如,"(.)\1" 表示两个连续的相同字符。 |
|
模板中的一个元素可以出现一次或多次。下面通过几个关于 \d 符号的示例来说明,该符号代表 1 个数字:
模板 | 含义 |
\d | 一个数字 |
\d? | 0 或 1 个数字 |
\d* | 0 个或更多数字 |
\d+ | 1 个或多个数字 |
\d{2} | 2个数字 |
\d{3,} | 至少 3 个数字 |
\d{5,7} | 5 到 7 位数字 |
现在设想一个能够描述字符串预期格式的模式:
要查找的字符串 | 模式 |
日期格式为 dd/mm/yy | \d{2}/\d{2}/\d{2} |
时长格式为 hh:mm:ss | \d{2}:\d{2}:\d{2} |
一个无符号整数 | \d+ |
一个可能为空的空格序列 | \s* |
一个无符号整数,其前后可能有空格 | \s*\d+\s* |
一个可能带符号的整数,其前后可能有空格 | \s*[+|-]?\s*\d+\s* |
一个无符号实数,其前后可能有空格 | \s*\d+(.\d*)?\s* |
一个可能带符号且前后带有空格的实数 | \s*[+|]?\s*\d+(.\d*)?\s* |
包含单词“just”的字符串 | \bjuste\b |
可以指定在字符串中搜索模式的位置:
模式 | 含义 |
^模式 | 模式位于字符串开头 |
模式$ | 模式结束字符串 |
^模式$ | 模式开头和结尾 |
模式 | 从字符串开头开始,在整个字符串中搜索该模式。 |
要搜索的字符串 | 模式 |
以感叹号结尾的字符串 | !$ |
以句点结尾的字符串 | \.$ |
以 // 序列开头的字符串 | ^// |
仅包含一个单词(前后可能有空格)的字符串 | ^\s*\w+\s*$ |
一个字符串,包含两个单词,前后可能有空格 | ^\s*\w+\s*\w+\s*$ |
包含单词 secret 的字符串 | \bsecret\b |
模式的子集可以被“提取”。因此,我们不仅可以验证一个字符串是否匹配特定模式,还可以从该字符串中提取那些用圆括号括起的、与模式子集对应的元素。 例如,若要分析包含日期格式 dd/mm/yy 的字符串,并提取其中的 dd、mm、yy 部分,可使用正则表达式 (\d\d)/(\d\d)/(\d\d)。
4.11.2. 验证字符串是否符合给定模式
类 Pattern 用于验证字符串是否符合给定模式。为此,我们使用静态方法
其中:modèle:待验证的模板,chaine:与模板进行比对的字符串。如果字符串与模板匹配,则返回布尔值true;否则返回false。
以下是一个示例:
import java.io.*;
import java.util.regex.*;
// 正则表达式管理
public class regex1 {
public static void main(String[] args){
// 正则表达式模板
String modèle1="^\\s*\\d+\\s*$";
// 将实例与模式进行比较
String exemplaire1=" 123 ";
if (Pattern.matches(modèle1,exemplaire1)){
affiche("["+exemplaire1 + "] correspond au modèle ["+modèle1+"]");
}else{
affiche("["+exemplaire1 + "] ne correspond pas au modèle ["+modèle1+"]");
}//if
String exemplaire2=" 123a ";
if (Pattern.matches(modèle1,exemplaire2)){
affiche("["+exemplaire2 + "] correspond au modèle ["+modèle1+"]");
}else{
affiche("["+exemplaire2 + "] ne correspond pas au modèle ["+modèle1+"]");
}//if
}//main
public static void affiche(String msg){
System.out.println(msg);
}//显示
}//类
以及执行结果:
需要注意的是,在正则表达式“^\s*\d+\s*$”中,由于 Java 对字符 \ 的特殊解释,该字符必须成对出现。因此,我们写为:String 模式1="^\\s*\\d+\\s*$";
4.11.3. 查找字符串中所有符合模式的元素
考虑正则表达式“\d+”和字符串“ 123 456 789 ”。该正则表达式在字符串中的三个不同位置出现。Pattern 和 Matcher 类可用于获取字符串中正则表达式的各个匹配项。 Pattern 类是管理正则表达式的类。被多次使用的正则表达式需要进行“编译”。这可以加快在字符串中搜索模式的速度。静态方法 compile 负责执行此工作:
该方法以模式字符串作为参数,并返回一个 Pattern 对象。若要将 Pattern 对象中的模式与字符串进行比较,则使用 Matcher 类。该类支持模式与字符串的比较。 基于 Pattern 对象,可通过 matcher 方法获取 Matcher 类型的对象:
input 是需要与模式进行比较的字符串。
因此,若要将模式“\d+”与字符串“ 123 456 789 ”进行比较,可按以下方式创建 Matcher 对象:
基于前面的 résultats 对象,我们可以检索字符串中模式的各个匹配项。为此,我们使用 Matcher 类中的 suivantes 方法:
方法 find 会在被分析的字符串中查找模式的首次出现。再次调用 find 将查找下一次出现。依此类推。 如果找到该模式,该方法返回 true;否则返回 false。 find 找到的最后一个模式匹配所对应的字符串片段可通过 group 方法获取,其位置则可通过 start 方法获取。 因此,如果延续前面的示例,并希望显示字符串“123 456 789”中所有“\d+”模式的出现位置,则应编写:
while(résultats.find()){
System.out.println("séquence " + résultats.group() + " trouvée en position " + résultats.start());
}//while
方法 reset 可将对象 Matcher 重置为与模式进行比较的字符串开头。因此,方法 find 随后将再次找到该模式的第一个匹配项。
以下是一个完整示例:
import java.io.*;
import java.util.regex.*;
// 正则表达式管理
public class regex2 {
public static void main(String[] args){
// 模板在实例中的多个实例
String modèle2="\\d+";
Pattern regex2=Pattern.compile(modèle2);
String exemplaire3=" 123 456 789";
// 在实例中搜索模式的出现
Matcher matcher2=regex2.matcher(exemplaire3);
while(matcher2.find()){
affiche("séquence " + matcher2.group() + " trouvée en position " + matcher2.start());
}//while
}//主
public static void affiche(String msg){
System.out.println(msg);
}//显示
}//分类
执行结果:
Modèle=[\d+],exemplaire=[ 123 456 789 ]
Il y a 3 occurrences du modèle dans l'exemplaire
123 en position 2
456 en position 7
789 en position 12
4.11.4. 提取模式的子集
可以“提取”模式的子集。因此,我们不仅可以验证字符串是否符合特定模式,还可以从该字符串中提取模式中用圆括号括起的子集对应的元素。 例如,若要解析包含日期格式 dd/mm/yy 的字符串,并提取其中的 dd、mm、yy 元素,可使用正则表达式 (\d\d)/(\d\d)/(\d\d)。
让我们来看以下示例:
import java.io.*;
import java.util.regex.*;
// 正则表达式管理
public class regex3 {
public static void main(String[] args){
// 在模板中捕获元素
String modèle3="(\\d\\d):(\\d\\d):(\\d\\d)";
Pattern regex3=Pattern.compile(modèle3);
String exemplaire4="Il est 18:05:49";
// 模板验证
Matcher résultat=regex3.matcher(exemplaire4);
if (résultat.find()){
// 实例与模板匹配
affiche("L'exemplaire ["+exemplaire4+"] correspond au modèle ["+modèle3+"]");
// 显示组
for (int i=0;i<=résultat.groupCount();i++){
affiche("groupes["+i+"]=["+résultat.group(i)+"] en position "+résultat.start(i));
}//for
}else{
// 实例与模板不匹配
affiche("L'exemplaire["+exemplaire4+" ne correspond pas au modèle ["+modèle3+"]");
}
}//主
public static void affiche(String msg){
System.out.println(msg);
}//显示
}//分类
运行该程序会产生以下结果:
L'exemplaire [Il est 18:05:49] correspond au modèle [(\d\d):(\d\d):(\d\d)]
groupes[0]=[18:05:49] en position 7
groupes[1]=[18] en position 7
groupes[2]=[05] en position 10
groupes[3]=[49] en position 13
新内容位于以下代码部分:
// 模板验证
Matcher résultat=regex3.matcher(exemplaire4);
if (résultat.find()){
// 实例与模板匹配
affiche("L'exemplaire ["+exemplaire4+"] correspond au modèle ["+modèle3+"]");
// 显示组
for (int i=0;i<=résultat.groupCount();i++){
affiche("groupes["+i+"]=["+résultat.group(i)+"] en position "+résultat.start(i));
}//用于
}else{
// 实例与模板不匹配
affiche("L'exemplaire["+exemplaire4+" ne correspond pas au modèle ["+modèle3+"]");
}
字符串 exemplaire4 通过方法 find 与模板 regex3 进行比较。 随后在字符串 exemplaire4 中找到了模式 regex3 的一个实例。如果模式包含用括号包围的子集,则可以通过 Matcher 类的各种方法访问这些子集:
public int groupCount()
public String group(int group)
public int start(int group)
方法 groupCount 返回在模板中找到的子集数量,group(i) 返回第 i 号子集。该子集位于字符串中由 start(i) 指定的位置。 例如:
L'exemplaire [Il est 18:05:49] correspond au modèle [(\d\d):(\d\d):(\d\d)]
groupes[0]=[18:05:49] en position 7
groupes[1]=[18] en position 7
groupes[2]=[05] en position 10
groupes[3]=[49] en position 13
首次调用方法 find 将找到字符串 18:05:49,并自动创建由模板中的括号定义的三个子集,分别是 18、05 和 49。
4.11.5. 一个学习程序
找到能够验证字符串是否符合特定模式的正则表达式,有时是一项真正的挑战。以下程序可用于练习。它要求输入一个模式和一个字符串,然后指出该字符串是否符合该模式。
import java.io.*;
import java.util.regex.*;
// 正则表达式管理
public class regex4 {
public static void main(String[] args){
// 数据
String modèle=null,chaine=null;
Pattern regex=null;
BufferedReader IN=null;
Matcher résultats=null;
int nbOccurrences=0;
// 错误管理
try{
// 要求用户提供与该实例进行比较的模板和实例
while(true){
// 输入流
IN=new BufferedReader(new InputStreamReader(System.in));
// 请求模板
System.out.print("Tapez le modèle à tester ou fin pour arrêter :");
modèle=IN.readLine();
// 完成?
if(modèle.trim().toLowerCase().equals("fin")) break;
// 创建正则表达式
regex=Pattern.compile(modèle);
// 询问用户要与模板进行比较的样本
while(true){
System.out.print("Tapez la chaîne à comparer au modèle ["+modèle+"] ou fin pour arrêter :");
chaine=IN.readLine();
// 完成?
if(chaine.trim().toLowerCase().equals("fin")) break;
// 创建匹配器对象
résultats=regex.matcher(chaine);
// 搜索模板的匹配项
nbOccurrences=0;
while(résultats.find()){
// 找到一个匹配项
nbOccurrences++;
// 显示该实例
System.out.println("J'ai trouvé la correspondance ["+résultats.group()
+"] en position "+résultats.start());
// 显示子元素
if(résultats.groupCount()!=1){
for(int j=1;j<=résultats.groupCount();j++){
System.out.println("\tsous-élément ["+résultats.group(j)+"] en position "+
résultats.start(j));
}//for j
}//if
// 下一行
}//while(résultats.find())
// 是否至少找到一个匹配项?
if(nbOccurrences==0){
System.out.println("Je n'ai pas trouvé de correspondance au modèle ["+modèle+"]");
}//if
// 下一个模式
}//while(true)
}//while(true)
}catch(Exception ex){
// 错误
System.err.println("Erreur : "+ex.getMessage());
// 带错误结束
System.exit(1);
}//try-catch
// 结束
System.exit(0);
}//Main
}//类
以下是一个执行示例:
Tapez le modèle à tester ou fin pour arrêter :\d+
Tapez la chaîne à comparer au modèle [\d+] ou fin pour arrêter :123 456 789
J'ai trouvé la correspondance [123] en position 0
J'ai trouvé la correspondance [456] en position 4
J'ai trouvé la correspondance [789] en position 8
Tapez la chaîne à comparer au modèle [\d+] ou fin pour arrêter :fin
Tapez le modèle à tester ou fin pour arrêter :(\d\d):(\d\d)
Tapez la chaîne à comparer au modèle [(\d\d):(\d\d)] ou fin pour arrêter :14:15
abcd 17:18 xyzt
J'ai trouvé la correspondance [14:15] en position 0
sous-élément [14] en position 0
sous-élément [15] en position 3
J'ai trouvé la correspondance [17:18] en position 11
sous-élément [17] en position 11
sous-élément [18] en position 14
Tapez la chaîne à comparer au modèle [(\d\d):(\d\d)] ou fin pour arrêter :fin
Tapez le modèle à tester ou fin pour arrêter :^\s*\d+\s*$
Tapez la chaîne à comparer au modèle [^\s*\d+\s*$] ou fin pour arrêter : 1456
J'ai trouvé la correspondance [ 1456] en position 0
Tapez la chaîne à comparer au modèle [^\s*\d+\s*$] ou fin pour arrêter :fin
Tapez le modèle à tester ou fin pour arrêter :^\s*(\d+)\s*$
Tapez la chaîne à comparer au modèle [^\s*(\d+)\s*$] ou fin pour arrêter :1456
J'ai trouvé la correspondance [1456] en position 0
sous-élément [1456] en position 0
Tapez la chaîne à comparer au modèle [^\s*(\d+)\s*$] ou fin pour arrêter :abcd 1
456
Je n'ai pas trouvé de correspondances
Tapez la chaîne à comparer au modèle [^\s*(\d+)\s*$] ou fin pour arrêter :fin
Tapez le modèle à tester ou fin pour arrêter :fin
4.11.6. Pattern 类的 split 方法
假设有一个字符串,其中各字段由一个通过正则表达式定义的分隔符分隔。例如,如果字段由字符 "," 分隔,且该字符前后可能跟随任意数量的空格,那么描述字段分隔符的正则表达式就是 "\s*,\s*"。 Pattern 类的 split 方法允许我们将字段提取到数组中:
public String[] split(CharSequence input)
字符串 input 被分解为各个字段,这些字段由与当前 Pattern 对象模型相对应的分隔符分隔。 若要提取某行数据中的字段,且该行的字段分隔符为逗号(前后可跟随任意数量的空格),则应编写如下代码:
// 一行
String ligne="abc ,, def , ghi";
// 一个模板
Pattern modèle=Pattern.compile("\\s*,\\s*");
// 行拆分为字段
String[] champs=modèle.split(ligne);
使用类 String 中的方法 split 也可以得到相同的结果:
public String[] split(String regex)
以下是一个测试程序:
import java.io.*;
import java.util.regex.*;
// 正则表达式管理
public class split1 {
public static void main(String[] args){
// 一行
String ligne="abc ,, def , ghi";
// 一个模板
Pattern modèle=Pattern.compile("\\s*,\\s*");
// 行拆分为字段
String[] champs=modèle.split(ligne);
// 显示
for(int i=0;i<champs.length;i++){
System.out.println("champs["+i+"]=["+champs[i]+"]");
}//for
// 另一种实现方式
champs=ligne.split("\\s*,\\s*");
// 显示
for(int i=0;i<champs.length;i++){
System.out.println("champs["+i+"]=["+champs[i]+"]");
}//for
}//主
}//类
执行结果:
champs[0]=[abc]
champs[1]=[]
champs[2]=[def]
champs[3]=[ghi]
champs[0]=[abc]
champs[1]=[]
champs[2]=[def]
champs[3]=[ghi]
4.12. 练习题
4.12.1. 练习 1
在 Unix 系统中,程序通常按以下方式调用:
$ pg -o1 v1 v2 ... -o2 v3 v4 …
其中 -oi 代表一个选项,vi 代表与该选项关联的值。我们希望创建一个 options 类,用于解析参数字符串 -o1 v1 v2 ... -o2 v3 v4 …,从而构建以下实体:
optionsValides | 一个哈希表(Hashtable),其键为有效的 oi 选项。与键 oi 关联的值是一个向量(Vector),其元素为与选项 -oi 关联的值 v1 v2 … |
optionsInvalides | 哈希表(Hashtable),其键为无效的 oi 选项。与键 oi 关联的值是一个向量(Vector),其元素是与选项 -oi 关联的值 v1 v2 … |
optionsSans | 字符串(String),列出了未与任何选项关联的值 vi |
erreur | 整数,若参数行中无错误则值为 0,否则为其他值: 1:存在无效的调用参数 2:存在无效选项 4:存在未与选项关联的值 如果存在多种类型的错误,这些值将累加。 |
可以通过 4 种不同方式构建 options 对象:
public options (String arguments, String optionsAcceptables)
arguments | 待分析的参数行 -o1 v1 v2 ... -o2 v3 v4 … |
optionsAcceptables | 可接受的 oi 选项列表 |
调用示例:options opt=new options("-u u1 u2 u3 -g g1 g2 -x","-u -g");
在此,两个参数均为字符串。当这些字符串被分割为单词并放入字符串数组中时,系统也会接受。这需要另外三个构造函数:
public options (String[] arguments, String optionsAcceptables)
public options (String arguments, String[] optionsAcceptables)
public options (String[] arguments, String[] optionsAcceptables)
该选项类将提供以下接口(访问器):
返回在创建选项对象时构建的数组 optionsValides 的引用
返回在创建选项对象时构建的数组 optionsInvalides 的引用
返回在创建选项对象时构建的字符串 optionsSans 的引用
返回在创建选项对象时生成的错误属性的值
如果没有错误,则显示属性 optionsValides、optionsInvalides、optionsSans 的值;否则显示错误编号。
以下是一个示例程序:
import java.io.*;
//导入选项;
public class test1{
public static void main (String[] arg){
// 打开输入流
String ligne;
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
affiche(e);
System.exit(1);
}
// 读取构造函数 options(String, string) 的参数
String options=null;
String optionsAcceptables=null;
while(true){
System.out.print("Options : ");
try{
options=IN.readLine();
} catch (Exception e){
affiche(e);
System.exit(2);
}
if(options.length()==0) break;
System.out.print("Options acceptables: ");
try{
optionsAcceptables=IN.readLine();
} catch (Exception e){
affiche(e);
System.exit(2);
}
System.out.println(new options(options,optionsAcceptables));
}// while 循环结束
}//main 结束
public static void affiche(Exception e){
System.err.println("Erreur : "+e);
}
}//类结束
部分结果:
C:\Serge\java\options>java test1
Options : 1 2 3 -a a1 a2 -b b1 -c c1 c2 c3 -b b2 b3
Options acceptables: -a -b
Erreur 6
Options valides :(-b,b1,b2,b3) (-a,a1,a2)
Options invalides : (-c,c1,c2,c3)
Sans options : 1 2 3
4.12.2. 练习 2
我们希望创建一个名为 stringtovector 的类,用于将对象 String 的内容转移到对象 Vector 中。该类将继承自类 Vector:
且其生成代码如下:
private void stringtovector(String S, String separateur, int[] tChampsVoulus,
boolean strict){
// 创建一个包含字符串 S 各字段的数组
// 该字符串由分隔符分隔的字段组成
// 若分隔符=null,则字符串仅构成一个字段
// 仅需要索引位于数组 tChampsVoulus
// 中的字段才被选中。索引从 1 开始
// 若 tChampsvoulus=null 或长度为零,则取所有字段
// 若 strict=true,则所有所需字段必须存在
该类将具有以下私有属性:
该属性由前面的构造函数设置,取值如下:
0:构造成功
4:当 strict=true 时,某些必填字段缺失
该类还将包含两个方法:
该方法返回私有属性 erreur 的值。
该方法以 (错误, 元素 1, 元素 2, …) 的形式显示对象的值,其中元素 i 是根据字符串构建的向量中的元素。
一个测试程序可以如下所示:
import java.io.*;
//import stringtovector;
public class essai2{
public static void main(String arg[]){
int[] T1={1,3};
System.out.println(new stringtovector("a : b : c :d:e",":",T1,true).identite());
int[] T2={1,3,7};
System.out.println(new stringtovector("a : b : c :d:e",":",T2,true).identite());
int [] T3={1,4,7};
System.out.println(new stringtovector("a : b : c :d:e",":",T3,false).identite());
System.out.println(new stringtovector("a : b : c :d:e","",T1,false).identite());
System.out.println(new stringtovector("a : b : c :d:e",null,T1,false).identite());
int[] T4={1};
System.out.println(new stringtovector("a : b : c :d:e","!",T4,true).identite());
int[] T5=null;
System.out.println(new stringtovector("a : b : c :d:e",":",T5,true).identite());
System.out.println(new stringtovector("a : b : c :d:e",null,T5,true).identite());
int[] T6=new int[0];
System.out.println(new stringtovector("a : b : c :d:e","",T6,true).identite());
int[] T7={1,3,4};
System.out.println(new stringtovector("a b c d e"," ",T6,true).identite());
}
}
结果:
(0,a,c)
(4,a,c)
(0,a,d)
(0,a : b : c :d:e)
(0,a : b : c :d:e)
(0,a : b : c :d:e)
(0,a,b,c,d,e)
(0,a : b : c :d:e)
(0,a : b : c :d:e)
(0,a,b,c,d,e)
一些建议:
- 要将字符串 S 拆分为字段,请使用类 String 中的方法 split。
- 将 S 中的字段放入以字段编号为索引的字典 D 中
- 从字典 D 中提取仅那些其键(索引)在数组 tChampsVoulus 中的字段。
4.12.3. 练习 3
希望向类 stringtovector 添加以下构造函数:
public stringtovector(String S, String separateur, String sChampsVoulus,boolean strict){
// 创建一个包含字符串 S 中各字段的向量
// 该字符串由分隔符分隔的字段组成
// 如果分隔符=null,则字符串仅构成一个字段
// 仅需要索引位于 sChampsVoulus 中的字段
// 索引从 1 开始
// 若 sChampsvoulus=null 或 "", 则取所有字段
// 如果 strict=true,则所有所需字段必须存在
因此,所需字段的列表是一个字符串(String),而不是一个整数数组(int[])。该类的私有属性 erreur 可以被赋予一个新值:
2:所需字段索引的字符串不正确
以下是一个示例程序:
import java.io.*;
//import stringtovector;
public class essai1{
public static void main(String arg[]){
String champs=null;
System.out.println(new stringtovector("a: b :c :d:e ",":","1 3",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":","1 3 7",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":","1 4 7",false).identite());
System.out.println(new stringtovector("a: b :c :d:e ","","1 3",false).identite());
System.out.println(new stringtovector("a: b :c :d:e ",null,"1 3",false).identite());
System.out.println(new stringtovector("a: b :c :d:e ","!","1",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":","",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":",champs,true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",null,champs,true).identite());
System.out.println(new stringtovector("a: b :c :d:e ","","",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":","1 !",true).identite());
System.out.println(new stringtovector("a b c d e "," ","1 3",false).identite());
}
}
部分结果:
(0,a,c)
(4,a,c)
(0,a,d)
(0,a: b :c :d:e)
(0,a: b :c :d:e)
(0,a: b :c :d:e)
(0,a,b,c,d,e)
(0,a,b,c,d,e)
(0,a: b :c :d:e)
(0,a: b :c :d:e)
(2)
(0,a,c)
一些建议:
- 需要参照前一个生成器的做法,将字符串 sChampsVoulus 中的字段转换为整数数组。 为此,需使用 StringTokenizer 对象将 sChampsVoulus 拆分为字段,该对象的 countTokens 属性将返回拆分出的字段数量。随后可创建一个尺寸合适的整数数组,并将其填充为拆分出的字段。
- 若要判断某个字段是否为整数,请使用方法 Integer.parseInt 将该字段转换为整数,并处理转换失败时产生的异常。
4.12.4. 练习 4
需要创建一个名为 filetovector 的类,用于将文本文件的内容传输到 Vector 对象中。该类将继承自 Vector 类:
并具有以下构造函数:
// --------------------- 构造函数
public filetovector(String nomFichier, String separateur, int [] tChampsVoulus,boolean strict, String tagCommentaire){
// 使用文本文件中的行创建向量nomFichier
// 行由分隔符分隔的字段组成
// 如果分隔符=null,则该行仅构成一个字段
// 仅需要索引位于 tChampsVoulus 中的字段
// 索引从 1 开始
// 若 tChampsvoulus=null 或为空,则取所有字段
// 若 strict=true,则所有所需字段必须存在
// 否则,该行不被存储,且其索引
// 将放入向量 lignesErronees
// 空行将被忽略
// 以及以 tagCommentaire 开头的行,如果 tagCommentaire != null
该类将具有以下私有属性:
属性 erreur 由前面的构造函数设置,其取值为:
0:构造成功
1:无法打开待处理的文件
4:在 strict=true 时,某些必需字段缺失
8:处理文件时发生 I/O 错误
属性 lignesErronees 是一个数组,其元素为错误行的编号(字符串形式)。当 strict=true 时,若某行无法提供所需字段,则该行被视为错误行。
该类还将包含两个方法:
该操作将私有属性的值设置为 erreur。
该函数以 (错误, 元素 1 元素 2 …,(l1,l2,…)) 的形式显示对象的值,其中元素 i 是根据文件构建的向量中的元素,l i 则是出错行的行号。
以下是一个测试示例:
import java.io.*;
//import filetovector;
public class test2{
public static void main(String arg[]){
int[] T1={1,3};
System.out.println(new filetovector("data.txt",":",T1,false,"#").identite());
System.out.println(new filetovector("data.txt",":",T1,true,"#").identite());
System.out.println(new filetovector("data.txt","",T1,false,"#").identite());
System.out.println(new filetovector("data.txt",null,T1,false,"#").identite());
int[] T2=null;
System.out.println(new filetovector("data.txt",":",T2,false,"#").identite());
System.out.println(new filetovector("data.txt",":",T2,false,"").identite());
int[] T3=new int[0];
System.out.println(new filetovector("data.txt",":",T3,false,null).identite());
}
}
执行结果:
[0,(0,a,c) (0,1,3) (0,azerty,cvf) (0,s)]
[4,(0,a,c) (0,1,3) (0,azerty,cvf),[5]]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# 注释) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# 注释) (0,azerty,1,cvf,fff,qqqq) (0,s)]
一些建议
-
文本文件按行处理。利用之前学过的stringtovector类,将每行拆分为字段。
-
因此,由文本文件构成的向量中的元素是stringtovector类型的对象。
-
filetovector 的 identite 方法可借助 stringtovector.identite() 方法显示其元素,并借助 Vector.toString() 方法显示可能存在的错误行号。
4.12.5. 练习 5
希望在 filetovector 类中添加以下构造函数:
public filetovector(String nomFichier, String separateur, String sChampsVoulus,
boolean strict, String tagCommentaire){
// 创建一个包含文本文件中各行的向量 nomFichier
// 行由分隔符分隔的字段组成
// 如果分隔符=null,则该行仅构成一个字段
// 仅需要索引位于 tChampsVoulus 中的字段
// 索引从 1 开始
// 若 sChampsvoulus=null 或为空,则取所有字段
// 若 strict=true,则所有所需字段必须存在
// 否则,该行不被存储,其索引
// 将放入向量 lignesErronees
// 空行将被忽略
// 以及以 tagCommentaire 开头的行,如果 tagCommentaire != null
所需字段索引的列表现在是一个字符串(String),而不是一个整数数组。
私有属性 erreur 可能具有一个额外值:
2:所需字段索引字符串不正确
以下是一个测试示例:
import java.io.*;
//import filetovector;
public class test1{
public static void main(String arg[]){
System.out.println(new filetovector("data.txt",":","1 3",false,"#").identite());
System.out.println(new filetovector("data.txt",":","1 3",true,"#").identite());
System.out.println(new filetovector("data.txt","","1 3",false,"#").identite());
System.out.println(new filetovector("data.txt",null," 1 3",false,"#").identite());
String S2=null;
System.out.println(new filetovector("data.txt",":",S2,false,"#").identite());
System.out.println(new filetovector("data.txt",":",S2,false,"").identite());
String S3="";
System.out.println(new filetovector("data.txt",":",S3,false,null).identite());
}
}
结果:
[0,(0,a,c) (0,1,3) (0,azerty,cvf) (0,s)][4,(0,a,c) (0,1,3) (0,azerty,cvf),[5]]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# 注释) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# 注释) (0,azerty,1,cvf,fff,qqqq) (0,s)]
一些建议
- 我们将字符串 sChampsVoulus 转换为整数数组 tChampVoulus,以便回归到前一个构造器的场景。