Skip to content

4. 常用类

本章将介绍若干常用的 Java 类。这些类拥有许多属性、方法和构造函数。每次我们仅介绍其中的一小部分。这些类的详细信息可在我们接下来介绍的 Java 帮助文档中查阅。

4.1. 文档

如果您已将 Sun 的 JDK 安装在 <jdk> 文件夹中,则文档位于 <jdk>\docs 文件夹内:

Image

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

Image

Image

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

Image

点击Java 2 Platform链接API

Image

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

让我们举个例子,查找关于实现动态数组的 Vector 类的信息。只需在左侧框架的类列表中查找 Vector 类的链接:

Image

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

Image

其中包含

  • 该类所属的类层次结构,此处为 java.util.Vector
  • 该类的字段(属性)列表
  • 构造函数列表
  • 方法列表

接下来,我们将介绍各种类。建议读者系统地查阅所用类的完整定义。

4.2. 测试类

下文中的示例有时会使用类 personneenseignant。在此重申其定义。

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 类似。它们的主要区别仅在于当多个执行线程同时使用时。 访问 VectorArrayList 时,线程的同步方法有所不同。除该情况外,两者可互换使用。以下是该类的一些字段、构造函数或方法:

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émenttableau 中的位置,否则返回 <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 是一个仅定义了一个方法的接口:

    int compare(Object o1, Object o2)

且该方法必须返回 0:若 o1=o2, -1:若 o1 < o2,+1:若 o1 > o2。在原型 void sort(Object[] obj, Comparator C) 中,第二个参数 C 必须是实现接口 Comparator 的对象。 在构造函数 sort2 中,我们选择了当前对象 this

     // 人员数组排序
    Arrays.sort(amis,this);

这要求我们完成两项操作:

  1. 声明类 sort2 实现了接口 Comparator
public class sort2 implements Comparator{
  1. 在类 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,这里使用的是年龄(也可以使用姓名)。

执行结果如下:

[tournesol,40]
[milou,80]
[tintin,100]

我们本可以采用不同的方式来实现接口 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。因此,其声明变为:

public class sort2 {

现在,我们通过以下示例测试类 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();
  }//主
}//班级

这里,我们的做法与之前的示例略有不同。 方法 sortbinarySearch 所需的两个对象 Comparator 已创建,并分配给变量 comparateur1comparateur2

     // 比较器
    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("milou",amis,comparateur2);
    cherche("xx",amis,comparateur2);

方法 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()
返回枚举中下一个元素的引用

如何使用枚举?通常如下所示:

    Enumeration e=                // 获取一个枚举对象
    while(e.hasMoreElements()){
        // 调用元素 e.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 如下:

Jean,Dupont,27
Pauline,Garcia,24
Gilles,Dumond,56

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);
  }

}// 类结束

运行该程序得到以下结果:

personne(Jean,Dupont,27)
personne(Pauline,Garcia,24)
personne(Gilles,Dumond,56)

4.9.3. 保存人员对象

我们将刚才所学的内容应用到类 personne 中,为其提供一个方法,用于将人员的属性保存到文件中。我们在类 personne 的定义中添加方法 sauveAttributs


    // ------------------------------
  // 保存到文本文件
    // ------------------------------
  public void sauveAttributs(PrintWriter P){
    P.println(""+this);
  }

在定义类 personne 之前,请务必导入包 java.io

import 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

struct article{
    char code[4];
    char nom[20];
    double prix;
    int stockActuel;
    int stockMinimum;
}//结构

因此,我们将代码限制为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
匹配 xy。例如,“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" 表示两个连续的相同字符。
\n
对应于 n,其中 n 是一个八进制转义值。八进制转义值必须包含 1、2 或 3 个数字。例如,“\11”和“\011”都对应于一个制表符。 "\0011" 等同于 "\001" & "1"。八进制转义值不得超过 256。如果超过,则表达式中仅考虑前两位数字。允许在正则表达式中使用 ASCII 代码。
\xn
对应于 n,其中 n 是一个十六进制转义值。 十六进制转义值必须包含两个数字。例如,“\x41”对应“A”。“\x041”等同于“\x04”和“1”。允许在正则表达式中使用代码 ASCII。

模板中的一个元素可以出现一次或多次。下面通过几个关于 \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 用于验证字符串是否符合给定模式。为此,我们使用静态方法

boolean Matches(String modèle, String chaine)

其中: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);
  }//显示
}//类

以及执行结果:

[  123  ] correspond au modèle [^\s*\d+\s*$]
[  123a  ] ne correspond pas au modèle [^\s*\d+\s*$]

需要注意的是,在正则表达式“^\s*\d+\s*$”中,由于 Java 对字符 \ 的特殊解释,该字符必须成对出现。因此,我们写为:String 模式1="^\\s*\\d+\\s*$";

4.11.3. 查找字符串中所有符合模式的元素

考虑正则表达式“\d+”和字符串“ 123 456 789 ”。该正则表达式在字符串中的三个不同位置出现。PatternMatcher 类可用于获取字符串中正则表达式的各个匹配项。 Pattern 类是管理正则表达式的类。被多次使用的正则表达式需要进行“编译”。这可以加快在字符串中搜索模式的速度。静态方法 compile 负责执行此工作:

public static Pattern compile(String regex)

该方法以模式字符串作为参数,并返回一个 Pattern 对象。若要将 Pattern 对象中的模式与字符串进行比较,则使用 Matcher 类。该类支持模式与字符串的比较。 基于 Pattern 对象,可通过 matcher 方法获取 Matcher 类型的对象:

public Matcher matcher(CharSequence input)

input 是需要与模式进行比较的字符串。

因此,若要将模式“\d+”与字符串“ 123 456 789 ”进行比较,可按以下方式创建 Matcher 对象:

Pattern regex=Pattern.compile("\\d+");
Matcher résultats=regex.matcher("  123  456  789  ");

基于前面的 résultats 对象,我们可以检索字符串中模式的各个匹配项。为此,我们使用 Matcher 类中的 suivantes 方法:

public boolean find()
public String group()
public int start()
public Matcher reset()

方法 find 会在被分析的字符串中查找模式的首次出现。再次调用 find 将查找下一次出现。依此类推。 如果找到该模式,该方法返回 true;否则返回 falsefind 找到的最后一个模式匹配所对应的字符串片段可通过 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)

该选项类将提供以下接口(访问器):

public Hashtable getOptionsValides()

返回在创建选项对象时构建的数组 optionsValides 的引用

public Hashtable getOptionsInvalides()

返回在创建选项对象时构建的数组 optionsInvalides 的引用

public String getOptionsSans()

返回在创建选项对象时构建的字符串 optionsSans 的引用

public int getErreur()

返回在创建选项对象时生成的错误属性的

public String toString()

如果没有错误,则显示属性 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

class stringtovector extends Vector

且其生成代码如下:


    private void stringtovector(String S, String separateur, int[] tChampsVoulus,
            boolean strict){
        
        // 创建一个包含字符串 S 各字段的数组
        // 该字符串由分隔符分隔的字段组成
        // 若分隔符=null,则字符串仅构成一个字段
        // 仅需要索引位于数组 tChampsVoulus 
        // 中的字段才被选中。索引从 1 开始
        // 若 tChampsvoulus=null 或长度为零,则取所有字段
        // 若 strict=true,则所有所需字段必须存在

该类将具有以下私有属性:

        private int erreur;

该属性由前面的构造函数设置,取值如下:

0:构造成功

4:当 strict=true 时,某些必填字段缺失

该类还将包含两个方法:

    public int getErreur()

该方法返回私有属性 erreur 的值。

    public String identite(){

该方法以 (错误, 元素 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)

一些建议:

  1. 要将字符串 S 拆分为字段,请使用类 String 中的方法 split
  2. 将 S 中的字段放入以字段编号为索引的字典 D 中
  3. 从字典 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)

一些建议:

  1. 需要参照前一个生成器的做法,将字符串 sChampsVoulus 中的字段转换为整数数组。 为此,需使用 StringTokenizer 对象将 sChampsVoulus 拆分为字段,该对象的 countTokens 属性将返回拆分出的字段数量。随后可创建一个尺寸合适的整数数组,并将其填充为拆分出的字段。
  2. 若要判断某个字段是否为整数,请使用方法 Integer.parseInt 将该字段转换为整数,并处理转换失败时产生的异常。

4.12.4. 练习 4

需要创建一个名为 filetovector 的类,用于将文本文件的内容传输到 Vector 对象中。该类将继承自 Vector 类:

class filetovector extends 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

该类将具有以下私有属性:

        private int erreur=0;
        private Vector lignesErronees=null;

属性 erreur 由前面的构造函数设置,其取值为:

0:构造成功

1:无法打开待处理的文件

4:在 strict=true 时,某些必需字段缺失

8:处理文件时发生 I/O 错误

属性 lignesErronees 是一个数组,其元素为错误行的编号(字符串形式)。当 strict=true 时,若某行无法提供所需字段,则该行被视为错误行。

该类还将包含两个方法:

    public int getErreur()

该操作将私有属性的值设置为 erreur

    public String identite(){

该函数以 (错误, 元素 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)]

一些建议

  1. 文本文件按行处理。利用之前学过的stringtovector类,将每行拆分为字段。

  2. 因此,由文本文件构成的向量中的元素是stringtovector类型的对象。

  3. filetovectoridentite 方法可借助 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)]

一些建议

  1. 我们将字符串 sChampsVoulus 转换为整数数组 tChampVoulus,以便回归到前一个构造器的场景。