第18章 文件属性查看器(GUI+文件操作)
本章通过Swing组件实现文件属性查看器界面,通过文件的操作来获取文件的相关属性并显示在界面中。“文件属性查看器”项目的实现,综合了图形用户界面的相关知识点和文件的操作。
本章的学习目标如下:
q 掌握组件和面板的使用方法;
q 了解文件的操作;
q 熟悉文件操作和访问的类。
18.1 文件属性查看器原理
“文件属性查看器”项目通过单击“查看”按钮,打开显示文件或目录的属性表格,这些文件或目录的具体位置为文本框中表示地址的字符串。
18.1.1 项目结构框架分析
文件属性查看器项目可利用Swing组件实现图形用户界面。文件属性查看器项目目录如图18.1所示,各个包的功能如下。
q 类FileAttrView:自定义窗口组件类。
q 类FileAttrFrame:利用Swing组件实现界面。
图18.1 项目目录
18.1.2 项目功能业务分析
本节将向读者介绍整个项目要实现的功能。这些功能包括文件属性查看器的初始化界面、查看已存在文件属性、查看已存在的目录属性,以及查看不存在的文件、目录属性和退出功能。
1.初始化界面
当运行文件属性查看器项目中的FileViewer类后,会出现如图18.2所示的初始界 面——文件属性查看器界面。
图18.2 初始化界面
2.查看已存在的文件属性
当出现初始化界面后,在“文件的地址”文本框中输入D:\\cjgong.txt字符串(已存在的文件地址),然后单击“查看”按钮,主界面的中间会显示出该文件的所有属性信息,具体过程如图18.3所示。
图18.3 查看已存在的文件
3.查看已存在的目录属性
当出现初始化界面后,在“文件的地址”文本框中输入D:\\cjgong字符串(已存在的目录地址),然后单击“查看”按钮,主界面的中间会显示出该目录的所有属性信息,具体过程如图18.4所示。
图18.4 查看已存在的目录
4.查看不存在的文件和目录属性
当出现初始化界面后,在“文件的地址”文本框中输入D:\\test字符串(不存在的目录地址),然后单击“查看”按钮,主界面的中间会显示出该目录的所有属性信息,具体过程如图18.5所示。
图18.5 查看不存在的目录过程
5.退出功能
当出现初始界面后,如果想实现退出功能,可以单击右上角的按钮,如图18.6 所示。
图18.6 退出功能
18.2 文件属性查看器项目
文件属性查看器项目具体程序架构如图18.7所示,它包含一个“文件属性查看器输入界面”的自定义窗口类FileAttrFrame.java,以及自定义窗口显示位置的类FileAttrView.java。
图18.7 程序关系图
18.2.1 实现显示文件信息的自定义窗口
FileViewer为“文件属性查看器”项目中的自定义窗口类,该类不仅继承了JFrame类,而且还实现了各个组件的相应功能,具体内容如代码18.1所示。
代码18.1 自定义窗口类:FileAttrFrame.java
public class FileAttrFrame extends JFrame {
//创建成员变量
private JPanel contentPane;
private BorderLayout borderLayout1 = new BorderLayout(); //创建布局管理器对象
private JTextField jTextField1 = new JTextField(); //创建文本域对象jTextField1
private JScrollPane jScrollPane1 = new JScrollPane(); //创建滚动面板对象jScrollPane1
private JTable jTable1; //创建表格对象
private JButton jButton1 = new JButton(); //创建按钮对象
File file; //创建文件对象
public FileAttrFrame() { //构造函数
//注册window事件
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit(); //调用jbInit()方法
} catch (Exception e) {
e.printStackTrace();
}
}
public void jbInit() throws Exception { //创建初始化方法
contentPane = (JPanel) this.getContentPane(); //为contentPane赋值
jTextField1.setText("文件的地址"); //为文本域对象设置文本
contentPane.setLayout(borderLayout1); //设置布局管理器
this.setSize(new Dimension(394, 164)); //设置大小
this.setTitle("文件属性查看器"); //设置标题
jScrollPane1.setAutoscrolls(true); //设置滚动面板对象
jButton1.setText("查看"); //设置按钮文本
//按钮的处理事件
jButton1.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
jButton1_actionPerformed(e); //调用相应的方法
}
});
//添加各个组件到主窗口中
contentPane.add(jButton1, BorderLayout.SOUTH);
contentPane.add(jTextField1, BorderLayout.NORTH);
contentPane.add(jScrollPane1, BorderLayout.CENTER);
}
//处理窗口的各种事件
protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e); //处理窗口关闭方法
if (e.getID() == WindowEvent.WINDOW_CLOSING) {
System.exit(0); //退出窗口
}
}
Object[] getfileInfoType() { //创建文件属性的标题名字数组
return new Object[] { "Name", "isFile", "length", "canRead",
"canWrite", "lastMotified", "isHidden" };
}
Object[][] getFileInfo(File file) { //获取文件属性信息数组
File theFile = file; //创建theFile对象
Object[][] data = new Object[1][7]; //文件信息数组变量data
if (theFile.exists())
return null;
//用File类的各种方法获取文件属性为数组data赋值
data[0][0] = theFile.getName(); //文件的名字
data[0][1] = String.valueOf(theFile.isFile()); //是否为文件
data[0][2] = String.valueOf(theFile.length()); //文件的大小
data[0][3] = String.valueOf(theFile.canRead()); //是否可读
data[0][4] = String.valueOf(theFile.canWrite()); //是否可写
data[0][5] = getDateString(theFile.lastModified()); //最后修改时间
data[0][6] = String.valueOf(theFile.isHidden()); //是否隐藏属性
return data; //返回数组对象
}
public static String getDateString(long mill) { //日期格式化方法
if (mill < 0)
return "";
Date date = new Date(mill);
Calendar rightNow = Calendar.getInstance();
rightNow.setTime(date);
int year = rightNow.get(Calendar.YEAR);
int month = rightNow.get(Calendar.MONTH);
int day = rightNow.get(Calendar.DAY_OF_MONTH);
int hour = rightNow.get(Calendar.HOUR_OF_DAY);
int min = rightNow.get(Calendar.MINUTE);
return year + "-" + (month < 10 ? "0" + month : "" + month) + "-"
+ (day < 10 ? "0" + day : "" + day)
+ (hour < 10 ? "0" + hour : "" + hour) + ":"
+ (min < 10 ? "0" + min : "" + min);
}
void jButton1_actionPerformed(ActionEvent e) { //按钮的事件监听器
file = new File(this.jTextField1.getText()); //为file对象赋值
//显示相应信息的表格
this.jTable1 = new JTable(this.getFileInfo(file), this
.getfileInfoType());
jScrollPane1.getViewport().add(jTable1, null); //添加到滚动面板上
}
}
【代码解析】
q 上述代码实现了文件属性查看器中的自定义窗口类,该用户界面涉及的具体容器、对象和布局如图18.8所示。
图18.8 布局
q 在上述代码中存在一个处理按钮的事件监听器方法jButton1_actionPerformed(),在该方法中首先通过获取到的地址创建一个文件对象file,然后把该文件的所有信息通过getFileInfo()方法存储到数组data中并同时显示在表格组件jTable1中,最后把该表格组件显示在滚动面板jScrollPane1上。
18.2.2 自定义窗口的显示
在18.2.1节创建了自定义窗口类,该窗口实现了“文件属性查看器”项目的全部功能。本节将讲解如何将自定义窗口在计算机屏幕上居中显示出来,具体内容如代码18.2所示。
代码18.2 实现窗口的显示:FileAttrView.java
public class FileAttrView {
private boolean packFrame = false; //创建布尔变量
public FileAttrView() { //构造函数
FileAttrFrame frame = new FileAttrFrame();//创建FileAttrFrame对象
if (packFrame) { //根据值来调用相应方法
frame.pack();
} else {
frame.validate();
}
//用来实现居中显示
Dimension screenSize = Toolkit.getDefaultToolkit().getScreen-
Size(); //获取屏幕大小
Dimension frameSize = frame.getSize(); //获取窗口大小
if (frameSize.height > screenSize.height) {
frameSize.height = screenSize.height;
}
if (frameSize.width > screenSize.width) {
frameSize.width = screenSize.width;
}
//设置窗口的位置
frame.setLocation((screenSize.width - frameSize.width) / 2,
(screenSize.height - frameSize.height) / 2);
frame.setVisible(true); //显示窗口
}
public static void main(String[] args) {
new FileAttrView(); //创建FileAttrView对象
}
}
【代码解析】
在上述代码中,为了使自定义窗口居中显示,首先获取计算机屏幕大小screenSize及自定义窗口的大小frameSize,然后通过表达式(screenSize.width-framesize.width)/2和(screenSize.height-frameSize.height)/2来获取中心位置的坐标。
18.3 知识点扩展——文件的操作和访问
在Java语言中存在I/O(Input/Output)机制,即输入和输出机制。通过I/O处理技术可以将数据保持到文本文件、二进制文件甚至ZIP压缩文件,以达到数据永远保存的要求。
18.3.1 通过FileOp类实现文件创建和删除功能
在Java语言中,目录是被当作一种特殊的文件来使用的。查看API帮助文档可以发现,File类是唯一代表磁盘文件对象的类。这里的文件本身不包含文件的内容部分。既然File类是文件的实体类,那么该类拥有了许多文件方面的属性和方法。
对于File类中提供的属性和方法,有一些是针对文件处理的,有一些是针对目录处理的,还有一些属于共用。由于该类的属性和方法太多,所以不能一一列举,下面通过FileOp类的实例,来讲解File类的一些主要属性和方法,具体内容如代码18.3所示。
代码18.3 文件的基本操作:FileOp.java
public class FileOp {
public static void main(String[] args) {
File f = new File("c:\\1.txt"); //创建文件对象
if (f.exists()) //判断文件是否存在
f.delete(); //如存在则删除
else //如不存在则创建
try {
f.createNewFile();
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("File name:" + f.getName()); //输出文件的名字
System.out.println("File path:" + f.getPath()); //输出文件的路径
System.out.println("Abs path:" + f.getAbsolutePath()); //输出文件的绝对路径
System.out.println("Parent:" + f.getParent()); //输出文件的父路径
System.out.println(f.exists() ? "exists" : "does not exist"); //文件是否存在于磁盘中
System.out.println(f.canWrite() ? "is writeable" : "is not
writeable"); //是否可写
System.out.println(f.canRead() ? "is readable" : "is not readable"); //是否可读
System.out.println(f.isDirectory() ? "is " : "is not" + " a
directory"); //是否是目录
System.out.println(f.isFile() ? "is normal file"
: "might be a named pipe"); //是否是文件
//是否绝对路径
System.out.println(f.isAbsolute() ? "is absolute" : "is not
absolute");
System.out.println("File last modified:" + f.lastModified()); //输出文件的最后修改时间
System.out.println("File size:" + f.length() + " Bytes"); //输出文件的大小
}
}
运行FileTest.java类,当文件c:\1.txt存在时,控制台窗口如图18.9所示;当文件不存在时,控制台窗口如图18.10所示。
【代码解析】
在上述代码中,首先创建了与文件1.text相关联的File类对象f,然后通过该对象的exists()方法判断1.text文件是否存在。如果存在就通过该对象的delete()方法删除;否则通过该对象的createNewFile()方法实现该文件的创建。最后,通过对象的各种属性和方法输出所创建文件1.text的各种属性。
图18.9 文件存在时控制台窗口显示 图18.10 文件不存在时控制台窗口显示
18.3.2 通过FileDir类实现列举文件和目录的功能
Java使用FileDir类来列举文件和目录,下面举例说明,如代码18.4所示。
代码18.4 文件列举的基本操作:FileDir.java
public class FileDir {
public static void main(String argv[]) {
File f = new File(System.getProperty("user.dir")); //创建File对象
File files[] = f.listFiles(); //获取File对象下的所有文件数组
for (int i = 0; i < files.length; i++) { //遍历文件数组
if (files[i].isDirectory()) //判断是否为目录
System.out.print("<Dir>\t");
else
System.out.print("\t" + files[i].length());//输出文件的大小
System.out.println("\t" + files[i].getName()); //输出文件的名字
}
}
}
运行FileDir.java类,控制台窗口如图18.11所示,而用来列举文件的文件夹目录如图18.12所示。
图18.11 运行结果 图18.12 文件夹目录
【代码解析】
在上述代码中,首先创建了与系统属性变量user相对应的文件夹相连的File对象f,然后通过该对象的listFiles()方法获取文件数组,最后遍历该数组,如果为文件类型,则输出文件的长度和名字;如果为目录,则输出目录名字。
18.3.3 File类提供的属性和方法
通过API帮助文档可以查看File类提供的属性和方法,如下所示。
1.构造函数
对于文件操作方法,首先从File类的构造方法说起,该类具有3种构造函数,它们的语法格式分别如下:
(1)File (String pathname)
参数pathname表示文件的路径名(包含文件名),该函数用于创建一个指定路径的 文件。
%注意:对于参数pathname,一般格式为“路径\文件名”格式,如果没有路径名称,则在程序运行的目录下创建文件。
(2)File (String path, String filename)
参数path表示文件的父路径名(不包含文件名),参数filename为文件的名字,该函数用于创建一个指定路径和名字的文件。
(3)File (File parent, String filename)
参数parent表示文件的父路径,参数filename为文件的名字(包含子路径),该函数用于创建一个指定路径和名字的文件。
在Windows系统中目录的分隔符号为“\”(separator),而在其他系统中却不是这样,例如Linux系统中是“/”。为了写出通用的程序,在具体设置目录时,首先需要通过System..getProperty("os.name")获取操作系统,然后通过File类中分隔符的常量具体设置。目录和路径要区分开,目录里的是目录分隔符,而路径里的是路径分隔符,分别对应于File类中的4个常量,即separator、separatorChar和pathseparator、pathseparatorChar。
%注意:separator是Sting类型,而separatorChar是Char类型。同理,pathseparator是Sting类型,而pathseparatorChar是Char类型。
2.路径的方法
在构造函数中存在一个重要的参数,即路径的参数。当获取File类对象后,如何获取路径的属性呢?查看API帮助文档,可以发现如下方法。
q getName()方法:获取与File对象相连接的文件或目录的名称(不包含路径名称)。
q getPath()方法:获取与File对象相连接的文件或目录的名称(包含路径名称)。
q getAbsolutePath()方法:获取与File对象相连接的文件或目录的绝对路径名称。
q getParent()方法:获取与File对象相连接的文件或目录的父路径名称。
q isAbsolute ()方法:判断与File对象相连接的文件或目录的父路径是否绝对路径。
例如创建一个File类对象,该程序是在D:\Java目录里运行,具体代码如下:
File test=new File("test","testfile.text");
对于对象test,调用getAbsolutePath()方法将返回D:\Java\test\testfile.text绝对路径;调用getPath()方法则返回test\testfile.text绝对路径;而调用getName()方法和getParent()方法将分别返回testfile.text和testfile.text路径。有时还需要判断File类对象是否使用了绝对路径,这时就需要通过isAbsolute ()方法来判断,对于test对象将返回false,即不是绝对路径。如果修改test对象如下,则返回true(使用了绝对路径)。
File test=new File("D:\\Java\\test\\testfile.text");
3.操作文件和目录——创建文件方法
如果想创建一个文件,首先需要判断该文件是否存在,然后才能创建。查看API帮助文档可以发现如下方法。
q exists()方法:检查与File对象相连接的文件和目录是否存在于磁盘中。
q createNewFile()方法:如果与File对象相连接的文件不存在,则创建一个空文件。
q createTempFile()方法:创建一个File对象并同时在磁盘上创建指定的文件。
%注意:createNewFile()方法通常与exists()方法相配合使用,而createTempFile()方法是一个类方法,其参数也具有特殊的规定。
4.操作文件和目录——创建目录方法
如果想创建一个文件,首先需要判断该文件是否存在,然后才能创建。查看API帮助文档可以发现如下方法。
q mkdir()方法:创建与File对象相连接的目录名称。
q mkdirs()方法:创建与File对象相连接的目录名称,如果父目录不存在,系统会自动生成。
%注意:上述两个方法的区别在于,如果要创建D:\Java\test这个目录,但是D:\Java不存在,这时如果用mkdir()方法创建,则不会成功;用mkdirs()方法创建会成功。
5.操作文件和目录——删除方法
如果想删除一个文件和目录,首先需要判断该对象是目录还是文件,然后才能删除。查看API帮助文档可以发现如下方法。
q isDirectory()方法:检查与File对象相连接的对象是否为目录。
q isFile()方法:检查与File对象相连接的对象是否为文件。
q delete()方法:删除与File对象相连接的文件和目录。
q deleteOnExit()方法:删除与File对象相连接的文件和目录,其不会立即运行,而是在整个程序结束时才会被执行。
%注意:在删除之前之所以要判断是目录还是文件,因为如果是目录,则需要判断目录下是否有文件和子目录,只有没有文件或子目录的情况下才可以正常删除。
6.操作文件和目录——列举方法
有时需要实现列举一个目录下的所有子目录和文件的功能,查看API帮助文档可以发现如下方法。
q list方法:返回与File对象相连接的目录下的所有子目录和文件。
q listFile()方法:返回与File对象相连接的目录下的所有文件。
q listRoots()方法:返回与File对象相连接的对象所属的根目录,即磁盘符号。
7.文件的属性
如果想删除一个文件和目录,首先需要判断该对象是目录还是文件,然后才能删除。查看API帮助文档可以发现如下方法。
q canRead()方法:判断与File对象相连接的文件是否可以读取里面的数据。
q canWrite()方法:判断与File对象相连接的文件是否可以写入数据。
q isHidden()方法:判断与File对象相连接的文件和目录是否隐藏。
q length()方法:返回与File对象相连接的文件的大小。
q lastModified()方法:返回与File对象相连接的文件的最后修改时间。
q setLastModified()方法:设置与File对象相连接的文件的最后修改时间。
18.3.4 文件访问的基本概念
如果想彻底理解文件的访问功能,必须先理解Stream(流)这个概念。当用水管浇花、洗澡等时,水是从水龙头经水管的一端流向另一端,而且只要水龙头不断的有水送出来,那么水就会接连不断地经水管流向另一端。这就是所谓的流概念。
文件的访问就是以上述概念来处数据的输入和输出的,InputStream和OutputStream是所有以字节为单位的输入和输出类的父类,而Reader和Writer是所有以字符为单位读取和输出类的父类。下面详细讲解这4个类的基础知识。
1.InputStream类
InputStream是用来实现输入字节的类,即读取数据的意思。如果要实现读取功能,就需要用到read()方法,该类提供了3种重载的方法,分别如下。
abstract int read()
该函数无任何参数,用来实现一次读取一个字节的数据,并以int类型返回读取的数据。如果没有数据,将返回–1。
int read(byte[] b)
参数b为缓冲区数组,该函数用来实现一次读取一个字节的数据,读取的数据会存储到缓冲区数组里,其返回的是真正读取的字节数目。
int read(byte[] b, int off, int len)
参数off为缓冲区数组的起始位置,参数len为一次要读取多少个字节数组。该函数用法与第2种重载方法一样。
由于有些数据源一次只能允许一个流来传送数据,所以如果用完了InputStream对象,则通过close()方法来关闭对象。
如果想知道流中还有多少个字节的数据,可以通过available()方法来实现,如下所示。
int available()
该方法会返回一个int类型数据,表示还有多少个字节的数据可以读取。
%注意:如果用InputStream对象调用该方法,只会返回0。该方法必须由继承InputStream的子类对象调用才能返回正确的数据。
如果想跳过流中的某些数据,可以通过skip()方法来实现,如下所示。
long skip(long n)
参数n表示要跳过几个字节的数据,该函数用来实现跳过指定字节的数据,返回真正跳过的字节数据。
对于流中的数据,如果想返回重新读取,可以先通过mark()方法设置回头的位置,然后通过reset()方法使流返回到设置的位置处。
void mark(int readlimit)
参数readlimit为返回的位置,在此输入流中标记指定的位置。
void reset()
该函数用来实现将流重新定位到对输入流最后调用mark()方法时的位置。
%注意:对于上述两个方法,不是每个InputStream子类都可以使用的,所以需要通过markSupported()方法来判断类是否支持这两个类。
2.OutputStream类
OutputStream是用来实现输出字节的类,即写入数据的意思。如果要实现写入功能,就需要用到write()方法,该类提供了3种重载的方法,分别如下。
abstract void write(int b)
参数b为字节的数目,该函数用来实现把b.length个字节写入输出流。
void write (byte[] b)
参数b为指定的字节数组,该函数用来实现把指定的字节数组写入输出流。
void write (byte[] b, int off, int len)
参数off为缓冲区数组的起始位置,参数len为一次要读取多少个字节数组。该函数用法与第2种重载方法一样。
由于有些数据源一次只能允许一个流来传送数据,所以如果用完OutputStream对象后,同样需要通过close()方法来关闭对象。
当用write()方法输出数据时,这些数据并不会马上输出到指定的目的文件里,而是先存储到内存的缓冲中。如果想把数据立即输出到目的文件中,可以调用flush()方法,该方法的语法如下:
void flush()
该函数用来实现刷新输出流并强制输出缓冲中的所有字节。
%注意:对于输出流,在调用close()方法前,一般会先调用flush()方法确保把所有的数据都输出到目的文件里。
3.Reader类
Reader是用来实现输入字符的类,即读取字符数据的意思。与InputStream类相比,Reader类操作的对象为char类型,而InputStream类为byte类型。所以只有把InputStream类提供的方法中,在用到byte类型的地方改为char类型就可以被使用。
%注意:在Reader类中用ready()方法代替InputStream类中的available()方法,表示已经准备好输入数据。
4.Writer类
Writer是用来实现输出字符的类,即写入字符数据的意思。与OutputStream类相比,Writer类操作的对象为char类型,而OutputStream类为byte类型。所以只有把OutputStream类提供的方法中,在用到byte类型的地方改为char类型就可以被使用。对于write()方法,Writer类比OutputStream类多了两个重载的方法,分别如下:
void write(String str)
参数str为写入的字符串,该函数用来实现把指定的字符串写入输出流。
void write(String str, int off, int len)
参数off为写入字符串的起始位置,参数len为一次要读取多少个字符。该函数用法与第2种重载方法一样。
18.3.5 文件的基本访问方式——字节方式
18.3.4节讲解了文件的操作方法和文件访问的父类,本节将详细讲解如何实现对文件的访问。查看API帮助文档,可以发现有3种方式来访问文件,本节将详细讲解以字节方式(类FileInputStream和FileOutputStream)访问文件的内容。
下面通过实现文件访问功能的ByteAccess类来讲解FileInputStream和FileOutputStream类的基本用法,具体内容如代码18.5所示。
代码18.5 文件的访问:ByteAccess.java
public class ByteAccess {
//创建成员变量
public File file = new File("C:\\", "Byte.txt");
public byte bytes[] = new byte[512];
public static void main(String args[]) { //主函数
ByteAccess test = new ByteAccess(); //创建ByteAccess方式
test.writemethod(); //调用写入方法
test.readmethod(); //调用读取方法
}
public void writemethod() { //写入方法
int b; //定义一个变量
System.out.println("请输入存入文本的内容:"); //输出相应提示信息
try {
if (!file.exists()) //判断文件是否存在
file.createNewFile(); //创建新文件
b = System.in.read(bytes); //把从键盘输入的字符存入bytes里
//创建文件输出流
FileOutputStream fos = new FileOutputStream(file, true);
fos.write(bytes, 0, b); //把bytes写入到指定文件中
fos.close(); //关闭输出流
} catch (IOException e) {
e.printStackTrace();
}
}
public void readmethod() { //读取方法
try {
FileInputStream fis = new FileInputStream(file); //创建文件字节输入流
int rs = 0; //创建变量rs
System.out.println("开始读取文件内容:"); //输出相应提示
while ((rs = fis.read(bytes, 0, 512)) > 0) { //读取文件内容
//在循环中读取输入流的数据
String s = new String(bytes, 0, rs);
System.out.println(s);
}
fis.close(); //关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行ByteAccess.java类之前,C盘里Byte.txt文件的内容如图18.13所示,按照如图18.14所示的运行过程运行ByteAccess类后,C盘里Byte.txt文件的内容如图18.15所示。
图18.13 程序运行前的文件
图18.14 运行过程
图18.15 程序运行后的文件 |
q 在写入数据的方法writemethod()中,首先判断指定的文件是否存在,如果不存在,则调用createNewFile()方法进行创建,然后获取从键盘中输入字符,最后通过创建文件输出流的write()方法,把字符字节数组输出到文件里。
q 在读取数据的readmethod()方法中,首先创建与文件相关联的读取流,然后在循环中通过文件流的read()方法把文件中的内容读取到字节数组中,最后再输出字节数组中的内容。
18.3.6 文件的基本访问方式——字符方式
18.3.5节讲解了如何通过字节方式来访问文件,本节将详细讲解如何通过字符方式来访问文件,即通过FileInputStream和FileOutputStream类实现对文件的访问。
下面通过实现文件访问功能的CharAccess类,来讲解Filereader和FileWriter类的基本使用方法,具体内容如代码18.6所示。
代码18.6 文件的访问:CharAccess.java
public class CharAccess {
//创建成员变量
File filein = new File("C:\\char.txt"); //创建原文件对象filein
File fileout = new File("C:\\char-1.txt"); //创建复制后的文件对象fileout
public char chars[] = new char[512]; //创建字符数组chars
public static void main(String args[]) { //主函数
CharAccess test = new CharAccess (); //创建CharAccess对象
test.readmethod(); //调用读取方法
test.writermethod(); //调用写入方法
}
public void writermethod() { //复制一个文件的内容到另一个文件中
try {
//创建新文件
if (!fileout.exists()) //如果文件不存在
fileout.createNewFile();
//创建源文件的读取流和目的文件的写入流
FileReader fin = new FileReader(filein);
FileWriter fos = new FileWriter(fileout);
int is; //变量
while ((is = fin.read()) != -1) { //遍历读取流
fos.write(is);
}
fin.close(); //关闭读取流
fos.close(); //关闭写入流
} catch (IOException e) {
e.printStackTrace();
}
}
public void readmethod() { //读取方法
try {
if (!filein.exists()) //如果文件不存在
filein.createNewFile(); //创建新文件
FileReader fin = new FileReader(filein); //创建源文件的读取流
int is; //创建变量
while ((is = fin.read(chars)) != -1) { //遍历读取
String str = new String(chars, 0, is); //创建字符串
//输出字符串
System.out.println("char文件的内容为:"+str);
}
fin.close(); //关闭读取流
} catch (IOException e) {
e.printStackTrace();
}
}
}
打开C盘可以发现char.text文件,该文件的内容如图18.16所示。运行CharAccess.java类,控制台窗口如图18.17所示。这时如果再次打开C盘,可以发现多出了一个名为char-1.text的文件,该文件的内容如图18.18所示。
图18.16 char.text文件的内容 图18.17 控制台窗口
图18.18 char-1.text文件的内容
【代码解析】
q 在读取数据的readmethod()方法中,首先创建与文件相关联的读取流,然后在循环中通过文件流的read()方法把文件中的内容读取到字符数组中,最后再输出字符数组组成的字符串内容。
q 在写入数据的writemethod()方法中,首先判断源文件和目的文件是否存在,如果不存在,则调用createNewFile()方法进行创建,然后获取该文件的输入流和输出流,最后在循环中把读取流中的字符通过输入流中的write()方法写入目的文件中,从而达到从源文件复制目标文件的效果。
18.3.7 文件的高级访问方式
18.3.6节讲解了文件访问的两种基本方式,本节将详细讲解文件访问的最后一种方 式——RandomAccessFile类的方式。查看API帮助文档,可以发现RandomAccessFile类可以实现对文件更高级的访问。下面将通过一个具体实例来讲解,具体步骤如下。
(1)下面通过实现文件访问功能的RandomAccess类来讲解RandomAccessFile类的基本使用方法,具体内容如代码18.7所示。
代码18.7 文件的访问:RandomAccess.java
public class RandomAccess {
public static void main(String[] args) throws Exception {
//创建3个雇员变量
Employee e1 = new Employee("zhangsan", 23);
Employee e2 = new Employee("cjgong", 24);
Employee e3 = new Employee("Wangwu", 25);
//创建一个文件的RandomAccessFile对象
RandomAccessFile ra = new RandomAccessFile("c:\\Random.txt", "rw");
//把3个雇员的信息写入文件里
ra.write(e1.name.getBytes());
ra.writeInt(e1.age);
ra.write(e2.name.getBytes());
ra.writeInt(e2.age);
ra.write(e3.name.getBytes());
ra.writeInt(e3.age);
ra.close(); //关闭ra流对象
//创建一个文件的RandomAccessFile对象
RandomAccessFile raf = new RandomAccessFile("c:\\Random.txt", "r");
int len = 8; //名字长度变量
//跳过第一个员工的信息,其中姓名8字节,年龄4字节
raf.skipBytes(12); //跳过12字节
System.out.println("第二个员工信息:");
String str = ""; //雇员信息变量
//通过循环为str赋值
for (int i = 0; i < len; i++)
str = str + (char) raf.readByte();
System.out.println("name:" + str.trim()); //输出雇员名字变量的值str
System.out.println("age:" + raf.readInt());//输出雇员的年龄
//输出第一个员工的信息
raf.seek(0); //将文件指针移动到文件开始位置
System.out.println("第一个员工的信息:");
str = "";
for (int i = 0; i < len; i++)
str = str + (char) raf.readByte();
System.out.println("name:" + str.trim());
System.out.println("age:" + raf.readInt());
//输出第三个员工的信息
raf.skipBytes(12); //跳过第二个员工的信息
str="";
System.out.println("第三个员工的信息:");
for (int i = 0; i < len; i++)
str = str + (char) raf.readByte();
System.out.println("name:" + str.trim());
System.out.println("age:" + raf.readInt());
raf.close(); //关闭流对象raf
}
}
打开C盘可以发现Random.text文件,该文件的内容如图18.19所示。运行RandomAccess.java类,控制台窗口如图18.20所示。这时如果再次打开C盘的char.text文件,该文件的内容如图18.21所示。
图18.19 程序运行前的文件 图18.20 控制台窗口 图18.21 程序运行后的文件
【代码解析】
q 对于文件Random.txt,首先通过与其关联的RandomAccessFile类型对象ra把雇员信息按照“第一雇员、第二雇员和第三雇员”的顺序写入文件。然后通过与其关联的RandomAccessFile类型对象raf把雇员信息按照“第二雇员、第一雇员和第三雇员”的顺序读取并输出到控制台窗口。
q 在具体写信息到文件的过程中,首先创建一个“读写”方式的RandomAccessFile类型对象ra,然后通过write()方法把所有“雇员”的姓名(name)信息写入文件中,writeInt()方法把所有“雇员”的年龄(age)信息写入文件中。
q 在具体从文件读取信息的过程中,需要创建一个“只读”方式的RandomAccessFile类型对象raf。对于第二个雇员信息的读取,首先需要通过skipBytes()方法跳过第一个雇员的信息,然后在遍历过程中通过readByte()方法把姓名(name)信息读出,最后通过readInt()方法把年龄(age)信息读出。对于第一个雇员信息的读取,首先需要通过seek()方法移动到文件的开始位置,然后在遍历过程中通过readByte()方法把姓名(name)信息读出,最后通过readInt()方法把年龄(age)信息读出。对于第三个雇员信息,同读取第二个雇员的过程基本一样,只是通过skipBytes()方法跳过第一个和第二个雇员的信息。
(2)创建一个封装“雇员”信息名为Employee的类,该类的具体内容如代码18.8所示。
代码18.8 雇员的类:Employee.java
class Employee {
String name; //雇员的名字
int age; //雇员的年龄
final static int LEN = 8; //雇员名字长度的常量
public Employee(String name, int age) { //构造函数
if (name.length() > LEN) { //当名字长度大于8个字符时
name = name.substring(0, 8); //截取前8个字符
} else { //当名字长度小于8个字符时
while (name.length() < LEN) //用空格填补成8个字符
name = name + "\u0000";
}
//为成员变量赋值
this.name = name;
this.age = age;
}
}
【代码解析】
q 由于一个“雇员”信息就是文件中的一条记录,而对于记录来说需要保证其大小相同,因此每个“雇员”的姓名(name)和年龄(age)属性值在文件中的长度是一样的。
q 对于年龄(age)属性,由于其是int类型,所以每个成员的年龄(age)属性值长度相同。对于姓名(name)属性,由于其是String类型,即长度不确定,所以需要在构造函数中通过判断语句对其值进行操作。当长度大于8个字符时,则截取前8个字符;当长度小于8个字符时,则补空格(\u0000)。
(3)通过API帮助文档来查看,以高级方式访问文件涉及的RandomAccessFile类的一些基础知识,分别如下:
类RandomAccessFile存在两种构造函数,分别如下所示。
RandomAccessFile(File file, String mode)
参数file表示文件对象,参数mode为操作文件的方式,该函数用于创建一个与指定文件对象相关联的写入或读取数据的随机存取文件流。
RandomAccessFile(String name, String mode)
参数name表示文件的路径(包含文件名),该函数用于创建一个与指定文件对象相关联的写入或读取数据的随机存取文件流。
上述构造函数中的参数mode表示访问文件的权限,其取值如表18.1所示。
表18.1 mode的值
值 | 意 义 |
r | 只读方式 |
rw | 可读写 |
rws | 同步写入 |
rwd | 更新同步写入 |
18.4 小 结
本章主要通过对文件的操作来获取文件的属性,并把相应的文件属性内容显示在相应的Swing组件里。虽然设计图形用户界面很重要,但是实现图形用户界面中组件的互访更重要。
在本章的最后还详细介绍了文件操作和访问的基础知识,对于文件的访问介绍了3种方式,分别为通过字节方式访问、通过字符方式访问和通过高级方式访问。