一、作业内容
使用字符流和GUI类,编程实现以下功能
(1)设计图形化界面,至少包括文本类控件类。接受从键盘输入姓名、学号、成绩并保存到文本文件中,可重复进行。
(2)从文件中读取各学生的成绩,并计算所有学生成绩的平均值、最大值和最小值,排序后输出到另一文本
二、作业分析
对于本次作业的需求,我们可以将任务分解为如下几个部分。
下面我们依次进行解析。
1、界面的设计
首先看一下成品的界面
可以很明显的看出窗口中含有的组件有:三个JLabel,三个JTextfield、两个JButton和一个JTextArea。那么我们在类中就可以首先先定义这些组件,之后在构造函数中对这些组件进行初始化,设置好大小,对两个按钮加上监听器,就完成了界面的编写。
在这部分内容中需要注意的地方是:因为我们打开这个程序是为了添加学生并且进行计算并保存到文件里,所以在初始化的时候,我们可以在指定位置先创建一个文件,之后都添加到那个文件里去,创建的操作是:
File file=new File("E:\\Java\\HomeWork4\\src\\FileGui\\test1.txt");
//判断该文件是否在硬盘中真实存在
if(!file.exists()){
try {
file.createNewFile();
//System.out.println("创建文件成功了");
} catch (IOException e) {
e.printStackTrace();
//System.out.println("创建文件失败了");
}
}
将path指定为我所希望文件创建的具体路径,之后判断文件是否存在,不存在就创建,否则提示文件创建失败,这里可以用area.apend()将提示显示到界面中。
2、读取和写入信息
在讲解监听器和排序之前,我们先讲一下如何将信息写入到文件中,而且是以追加形式写入,而不是覆盖。写入信息,我们可以使用FileOutputStream或者FileWriter,在这里我选择的是FileOutputStream,依然首先创建它的对象
FileOutputStream fileOutputStream = new FileOutputStream("E:\\Java\\HomeWork4\\src\\FileGui\\test1.txt", true);
注意后面一定要加true,因为这代表了信息是以追加的形式写入的。我们知道FileOutputStream是字节流形式,而FileWriter是字符流形式,所以我们要进行输入时应该要将输入的内容以字节形式写入。形式为:
fileOutputStream.write(s.getBytes());
可能你发现,这个s是什么呢?我要说s就是对三个JTextField的内容之和,我们通过JTextField里面一个方法getText可以以字符串得到文本框中的内容,而字符串可以进行拼接,所以s就是三个字符串拼接而成的信息。另外这个部分很重要的一点是!!!为了让信息写入后更整齐一点,我在姓名和学号字符串之间加了一个空格,在学号和成绩之间也加了一个空格,在成绩后面加了一个换行符。这样会让文件看起来更美观,同时我们在之后读取文件进行计算的时候也很方便。
这个部分我建议大家加的一个功能是:当用户输入的信息不全的时候,拒绝为其添加学生信息。比如用户只输入了一个姓名,却没有输入学号和成绩,这样是肯定无法计算的,所以我们不能将它写入文件。这部分内容怎么实施呢?很简单,在s+=field.getText()之前先判断一下field.getText()==""就行了,是的话提示用户重新输入。最后别忘了关闭文件哟!
好了,上面我们已经顺利的将信息输入到一个文件了,现在的需求是,在刚刚那份文件里读取信息,按照从小到大(或者从大到小)排序后再保存到另外一个文件去。排序等内容接下来再说,现在需要说的是读取。怎么读取文件呢?与OutputStream和FileWriter相对应的,InputStream和FileReader就是读取文件的类,其中InputStream是以字节形式读取,FileReader是以字符形式读取,为了用户的观感,我们使用FileReader进行读取。
老规矩,创建一个FileReader的对象,之后将它的路径定为刚刚写入信息的那个文件。
Reader reader=new FileReader("E:\\Java\\HomeWork4\\src\\FileGui\\test1.txt");
标准的写法是这样的
int temp=reader.read();
while(temp!=-1)
{
char ch=(char)temp;
}
为什么是int temp而不是char temp呢?
因为它方法就是这样定义的。如果需要将它转换成我们能看懂的文本,直接char强转就可以了,如果读到末尾,就返回-1,所以我们循环的终止条件是判断temp是否为-1。
那么问题又来了,我们用什么形式的变量储存读入的信息呢?我用的是一个二维String数组,它的行为学生人数,宽为3(姓名、学号、成绩)。我们只需要将每一行的\n去掉,再将那一串文字按照空格分割为三个字符串,分别代表姓名学号成绩,再将它放到String 数组中就可以完美解决问题。
那么又有一个问题,如果我这样写有问题吗?
int i=0;
while (temp != -1) {
char ch = (char) temp;
str[i] = tempstr.split(" ");
i++;
temp = reader.read();
}
很明显是有问题的,问题在于!read的时候是一个字一个字的read,如果这么轻易的就让i++,那么数组很容易就越界了。正确的做法是,定义一个空字符串tempstr,然后依次读取字符,如果当前读取的不是\n,就让字符串加上当前读取的字符,如果当前读取的字符是\n,那么说明一个同学的信息已经读完了,需要将tempstr按照空字符串分割,并且用str[i]这个字符串数组保存,之后再让i++重复操作即可。这里说句题外话:在字符串中,分割字符串用split方法,传入的参数是你想要分割的标志,比如我想按照空格分割就传入空格。在字符串中,想要去除\n可以使用replaceAll方法,传入两个参数\n 和“”,它的意思是将\n转为空字符串,就相当于去掉了\n。
3.对获取的学生信息的排序
上面一条说道我们通过二维字符串数组获取到了一个str[cnt][3]的对象(cnt代表学生人数),现在如何排序是个问题,问题体现在:字符串不像整型可以直接比较;如何保证成绩排序的同时,保证学号和姓名的对应?
想了一会,我给出的方法是(肯定不是最好方法,只是个人想法):自己写一个Sort函数,在成绩变化的同时,交换另外两个字符串的值。
具体代码如下
static public double Sort(String[][]str) {
int avg=0;
for (int i = 0; i < str.length - 1; i++) {
for (int j = 0; j < str.length - 1 - i; j++) {
if (Integer.parseInt(str[j][2]) > Integer.parseInt(str[j + 1][2])) {
//完成两个字符串中成绩的排序
int temp = Integer.parseInt(str[j][2]);
str[j][2] = str[j + 1][2];
str[j + 1][2] = String.valueOf(temp);
//对学号和姓名同样交换
String _name = str[j][0];
str[j][0] = str[j + 1][0];
str[j + 1][0] = _name;
String _xuehao = str[j][1];
str[j][1] = str[j + 1][1];
str[j + 1][1] = _xuehao;
}
}
}
for(String []s:str)
{
avg+=Integer.parseInt(s[2]);
}
return avg/str.length;
}
上面就是一个很简单的冒泡排序,只不过我通过String.valueOf方法和Integer.parseInt等方法在字符串和整型之间做了些转换而已,可以发现,我每次交换的时候都是带着学号和姓名一起交换的,所以就可以保证三者的对应。
至于我为什么返回值设置为double型,相信大家看出来了,我返回的正是所有学生成绩的平均值,这样就省的我接下来的计算了。
4、获取成绩最大值、最小值、平均值
这个部分在实现了第三点之后就变得尤其简单,平均值是sort函数的返回值,最大值是str二维字符串的最后一个(从小到大排),最小值是str二维字符串的第一个。
我写这个部分的原因其实是我对这部分信息的展示方式,我没有在文本显示区域直接显示,而是建立了一个弹窗。
double avg=Sort(str);
String s="这些学生成绩的最大值是:";
s+=str[cnt-1][2];
s+=",最小值是:";
s+=str[0][2];
s+=",平均值是:";
s+=avg;
JOptionPane.showMessageDialog(null,s,"计算结果",JOptionPane.INFORMATION_MESSAGE);
JOptionPane是一个很好用的显示弹窗的类,想了解可以看看这份博客Java GUI弹窗。我将我要显示的信息输入进去,在点击保存并计算按钮是时候显示弹窗。效果如图:
5、监听器的绑定
很明显,两个按钮需要绑定监听器,当用户点击添加学生的时候,所要做的操作是上面讲输入时的,将JTextField的信息存入文件的操作。当点击计算并保存的时候,首先是将数据从第一个文件取出,然后通过二维字符串数组保存,进行排序后弹出弹窗,之后再将排序后的数据输出到第二个文件去。在输出到第二个文件的时候,我还是照常加上了空格符合换行符。
6、优化和完善
其实到这里,已经实现了题目的所有要求。但是就使用而言依然有很多瑕疵。
比如,当用户添加完一个学生之后,上一个学生的信息依然会留在JTextField上,我们所需要做的是,在添加完一次学生之后,就将JTextField的text设置为“”。
再比如,我们的JTextArea的作用是,每添加一个学生就要显示出来,如图
但是如果你不加一些操作,就会发现:这里面的文字可以随意删改,当数据多的时候,就看不见新添加的学生了。我们可以将area的setEditable设置为false,就不可以更改了;添加area到窗口是,使用Scrollpane形式,这样数据多的话就可以产生滚轮了。
还可以完善的是,当你点击添加学生的时候,如何判断学生是否添加呢?我可以给一个弹窗,当学生添加成功的时候,就弹出一个窗口告诉我们。
再比如,当你添加完三个学生之后就进行操作后保存,一个排序好的三个学生的信息已经输出到了文件2中,这时候如果你再添加一个学生,再点击操作后保存,就会在文件2中多出排序好的四个学生,这是因为文件2写入是以追加形式的,起初文件2已经有了三个学生信息了,后来又进来了四个学生信息,其中三个是已经出现过的了。我的解决方法是每次往文件1添加学生的时候就清空一次文件2,这样文件2就不会重复了(这个方法可能不是很好,但是我暂时想不到更好的)。
再比如,如果你重复添加了两个一模一样的信息,我怎么才能告诉你呢 ?这个问题可以通过遍历文件实现,但是我没有搞,还有很多别的事要做。不过我觉得到此为止,这已经是一个较为完善的程序了。
三、完整代码
package FileGui;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
/*使用 字符流和和GUI类 编程实现以下功能:
(1)设计图形化界面,至少包括文本类控件类。接收从键盘输入姓名、学号、成绩,并保存到文本文件中,重复进行。
(2)从文件中读取各学生的成绩,并计算所有学生成绩的平均值、最大值和最小值,排序后输出到另一文本文件。*/
public class FIleProcess extends JFrame implements ActionListener {
JLabel label_name,label_xuehao,label_grades;
JTextField field_name,field_xuehao,field_grades;
JTextArea area;
JButton button_add,button_baocun;
File file=null;
static int cnt=0;
public FIleProcess()
{
init();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
setBounds(400,200,700,400);
setResizable(false);
setTitle("作业四");
}
public void init()
{
//布局设置
area=new JTextArea(19,62);
area.setEditable(false);
label_grades=new JLabel("成绩:");
label_name=new JLabel("姓名:");
label_xuehao=new JLabel("学号");
field_grades=new JTextField(10);
field_name=new JTextField(10);
field_xuehao=new JTextField(10);
button_add=new JButton("添加学生");
button_baocun=new JButton("操作后保存");
add(new JScrollPane(area));
JPanel panel=new JPanel();
panel.add(label_name);
panel.add(field_name);
panel.add(label_xuehao);
panel.add(field_xuehao);
panel.add(label_grades);
panel.add(field_grades);
panel.add(button_add);
panel.add(button_baocun);
setContentPane(panel);
add(new JScrollPane(area));
//添加监听器
button_baocun.addActionListener(this);
button_add.addActionListener(this);
file=new File("E:\\Java\\HomeWork4\\src\\FileGui\\test1.txt");
//判断该文件是否在硬盘中真实存在
if(!file.exists()){
try {
file.createNewFile();
//System.out.println("创建文件成功了");
} catch (IOException e) {
e.printStackTrace();
//System.out.println("创建文件失败了");
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==button_add)
{
String s="";
if(field_name.getText().equals(""))
area.append("姓名不能为空\n");
else
{
s+=field_name.getText();
s+=" ";
}
if(field_xuehao.getText().equals(""))
area.append("学号不能为空\n");
else {
s += field_xuehao.getText();
s += " ";
}
if(field_grades.getText().equals(""))
area.append("成绩不能为空\n");
else
{
s+=field_grades.getText();
s+="\n";
}
if(!field_name.getText().equals("")&&!field_grades.getText().equals("")&&!field_xuehao.getText().equals("")) {
try {
FileOutputStream fileOutputStream = new FileOutputStream("E:\\Java\\HomeWork4\\src\\FileGui\\test1.txt", true);
fileOutputStream.write(s.getBytes());
FileOutputStream fileOutputStrea2 = new FileOutputStream("E:\\Java\\HomeWork4\\src\\FileGui\\test2.txt");
fileOutputStrea2.write("".getBytes());
fileOutputStrea2.flush();
fileOutputStrea2.close();
field_grades.setText("");
field_xuehao.setText("");
field_name.setText("");
JOptionPane.showMessageDialog(null, "添加成功", "关于", JOptionPane.INFORMATION_MESSAGE);
area.append("第" + (cnt + 1) + "个学生:" + s);
cnt++;
fileOutputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
else{
JOptionPane.showMessageDialog(null, "添加失败", "关于", JOptionPane.WARNING_MESSAGE);
}
}
if(e.getSource()==button_baocun)
{
try {
Reader reader=new FileReader("E:\\Java\\HomeWork4\\src\\FileGui\\test1.txt");
String [][]str=new String[cnt][3];
//System.out.println(cnt);
int temp=reader.read();
int i=0;
String tempstr ="";
while (temp != -1) {
//因为读取是按照字符一个个读取,所以i很容易变得很大
char ch = (char) temp;
if(ch!='\n')
tempstr +=String.valueOf(ch);
else {
str[i] = tempstr.split(" ");
i++;
tempstr="";
}
temp = reader.read();
}
//对数据进行排序
double avg=Sort(str);
String s="这些学生成绩的最大值是:";
s+=str[cnt-1][2];
s+=",最小值是:";
s+=str[0][2];
s+=",平均值是:";
s+=avg;
JOptionPane.showMessageDialog(null,s,"计算结果",JOptionPane.INFORMATION_MESSAGE);
FileOutputStream fileOutputStream = new FileOutputStream("E:\\Java\\HomeWork4\\src\\FileGui\\test2.txt", true);
for(String []tmpstr:str) {
fileOutputStream.write((tmpstr[0]+" ").getBytes());
fileOutputStream.write((tmpstr[1]+" ").getBytes());
fileOutputStream.write((tmpstr[2]+"\n").getBytes());
}
fileOutputStream.close();
} catch (FileNotFoundException ex) {
ex.printStackTrace();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
//返回的是平均值
static public double Sort(String[][]str) {
int avg=0;
for (int i = 0; i < str.length - 1; i++) {
for (int j = 0; j < str.length - 1 - i; j++) {
if (Integer.parseInt(str[j][2]) > Integer.parseInt(str[j + 1][2])) {
//完成两个字符串中成绩的排序
int temp = Integer.parseInt(str[j][2]);
str[j][2] = str[j + 1][2];
str[j + 1][2] = String.valueOf(temp);
//对学号和姓名同样交换
String _name = str[j][0];
str[j][0] = str[j + 1][0];
str[j + 1][0] = _name;
String _xuehao = str[j][1];
str[j][1] = str[j + 1][1];
str[j + 1][1] = _xuehao;
}
}
}
for(String []s:str)
{
avg+=Integer.parseInt(s[2]);
}
return avg/str.length;
}
public static void main(String[] args) throws IOException {
new FIleProcess();
}
}
四、结尾
感谢您能看到这里,如有错误,敬请斧正