一、题目与要求
1、题目
银行业务模拟与离散事件模拟
2、问题描述
假设某银行有4个窗口对外接待客户,从早晨银行开门(开门9:00am,关门5:00pm)起不断有客户进入银行。由于每个窗口在某个时刻只能接待一个客户,因此在客户人数众多时需要在每个窗口前顺次排队,对于刚进入银行的客户(建议:客户进入时间使用随机函数产生),如果某个窗口的业务员正空闲,则可上前办理业务;反之,若4个窗口均有窗户所占,他便会排在人数最少的队伍后面。
3、任务要求
(1)编制一个程序以模拟银行的这种业务活动并计算一天中客户在银行逗留的平均时间。
(2)建议有如下设置:
a) 客户到达时间随机产生,一天客户的人数设定为100人。
b) 银行业务员处理时间随机产生,平均处理时间10分钟。
(3)将一天的数据(包括业务员和客户)以文件方式输出。
二、程序设计过程
当我看到这20个题目的时候,我选择了“银行业务模拟与离散事件模拟”,因为我感觉这个做出来的话应该很好玩,当初也没有考虑太多这个选题的难易程度。
1、C语言版本
要真正的实现模拟银行办理业务必须是多线程,但是C语言只能实现单线程,我开始做这个的时候Java也还没有学到多线程。所以我就用单线程模拟着写了一个。
(1)程序分析
对于银行业务模拟,首先我定义了两个结构体,一个时间结构体,用来模拟系统时间,还有一个人员结构体。
struct arrivetime //时间结构体
{
int hour;
int minute;
};
struct person //人员结构体
{
int number; //排队号码/窗口人数
int dealtime; //预计处理时间
int windowsnumber; //窗口号码
int space; //人与人之间的总间距
arrivetime waitting; //到达时间
struct person *next;
};//结构体数组
之后我把人员进来,出去的时间延时都集中在了一秒钟,每一秒钟可能生成0~100个人,每生成了一个人就通过随机数,队列随机生成他们的个人信息,每一个人进来之后这个人就要判断哪一个窗口的人最少,然后进入那个窗口排队,因为我是通过时间延时来模拟的,所以每一秒中通过遍历四个窗口的头结点,把头结点中的处理时间自减1,当处理时间为0的时候,就把它移除队列。效果图如下:
2、Java版本
因为去找老师很多次了,当我用C语言做出来的时候,说实话我感觉我这个相对于其他同学的还是蛮简单的,正好那时我们也学到了多线程,所以我就尝试着用Java写了一个初步。中途因为不知道多线程如何去实现,所以问了老师,老师和我讲了一下这个基本的思路,回寝室后我自己又查阅了很多资料。最后通过生产者和消费者思想的模式实现了多线程。对于生产者线程来说,我就只生成一个客户,然后把这个客户分配到特定的消费者线程集合之中,消费者线程中有一个集合,该集合中又数据就运行处理,否则线程休眠。
(1)基本目录
(2)运行结果
程序运行之后按开始按钮则开启生产者线程
if (e.getSource() == button) {
if (producerThread != null) {
JOptionPane.showMessageDialog(null, "客户生成线程已经启动!");
return;
}
producerThread = new Thread(new CustomerComing());
producerThread.start();
}
按暂停按钮则中断生产者线程
if (e.getSource() == button_1) {
if (producerThread == null) {
JOptionPane.showMessageDialog(null, "客户生成线程已经停止!");
return;
}
producerThread.stop();
producerThread = null;
}
三、源码
1、C语言版本
#include<stdio.h>
#include<windows.h>
#include<stdlib.h>
#include<conio.h>
#include<time.h>
//函数声明
void showmenu(); //菜单显示
void timeshow(); //时间显示
void screen(); //大厅屏幕
void serving(); //服务区
void manarrive(); //客户随机到达
void deal(struct person *p,int n); //办理业务
int compare(struct person *a,struct person *b); //判断哪个窗口人少
void deltime(struct person *p); //每个窗口第一位客户的办理时间减少
void manleave(struct person *p); //客户离开
void showarrive(struct person *p,int n); //显示客户到达信息
void showleave(struct person *p,int n); //显示客户离开信息
int sort(); //窗口排序
//结构体
struct arrivetime { //时间结构体
int hour;
int minute;
};
struct person { //人员结构体
int number; //排队号码/窗口人数
int dealtime; //预计处理时间
int windowsnumber; //窗口号码
int space; //人与人之间的总间距
arrivetime waitting; //到达时间
struct person *next;
};//结构体数组
struct person windows[4]= {0,0,0,0}; //四个窗口
struct person *lastp[4];
//全局所需变量
int syshour=9; //系统时间
int sysminute=0;
int cnumber=0; //客户数目
//主函数
int main() {
char ch;
system("color f0"); //背景白色
for(int i=0; i<4; i++) {
lastp[i]=&windows[i];
windows[i].windowsnumber=i;//窗口标号
}
while(1) {
showmenu(); //过程显示
srand(time(NULL));
printf("程序正在运行中。。。\n");
printf("任意键暂停,Ese键退出\n");
Sleep(1*1000);
if(kbhit()) {
ch=getch();
if(27==ch)
break;
else
system("pause");
}
system("cls");
}
printf("程序结束!\n");
}
void showmenu() {
printf("\t银行业务模拟与离散事件模拟\n");
timeshow(); //时间显示
screen(); //大厅屏幕/客户到达/客户离开
serving(); //显示服务区
}
void timeshow() { //时间显示
printf("\t\t时间:%d:%d\n",syshour,sysminute+1);
if(sysminute>59) {
syshour++;
sysminute=0;
} else
sysminute+=1;
}
void manarrive() { //客户到达
//生成客户信息
struct person *newp;
newp=(struct person *)malloc(sizeof(struct person)); //客户生成
newp->number=cnumber;
newp->waitting.hour=syshour;
newp->waitting.minute=sysminute;
newp->dealtime=rand()%5+8; //处理时间为 8-12
//判断客户进入那个窗口
if(windows[0].number==0)
deal(newp,0);
else if(windows[1].number==0)
deal(newp,1);
else if(windows[2].number==0)
deal(newp,2);
else if(windows[3].number==0)
deal(newp,3);
else
deal(newp,sort());
}
int sort() {
return compare(&windows[compare(&windows[0],&windows[1])],
&windows[compare(&windows[2],&windows[3])]);
}
//判断哪个窗口人少
int compare(struct person *a,struct person *b) {
if(a->number<=b->number)
return a->windowsnumber;//窗口的标号
else
return b->windowsnumber;
}
//办理业务
void deal(struct person *p,int n) {
if(windows[n].number==0) {
windows[n].next=NULL;
lastp[n]=&windows[n];
}
lastp[n]->next=p;
p->next=NULL;
lastp[n]=p;
windows[n].number++;
showarrive(p,n);
}
//每个窗口第一位客户的办理时间减少
void deltime(struct person *p) {
struct person *searchp;
searchp=p->next;
searchp->dealtime--;
}
//客户离开
void manleave(struct person *p) {
struct person *searchp;
searchp=p->next;
p->next=searchp->next;
p->number--;
showleave(searchp,p->dealtime);
free(searchp);
}
//大厅屏幕
void screen() {
int n;
n=rand()%2;//每一时刻到达人数0-1
printf("\n\n银行大厅:\n");
printf("----------------------------------------------------------------------------\n");
for(int i=0; i<n; i++) {
cnumber++; //总人数
if(cnumber<=100)
manarrive(); //客户到达
}
for(int j=0; j<4; j++) {
if(windows[j].number!=0) {
deltime(&windows[j]);//每个窗口第一位客户的办理时间减少
if(windows[j].next->dealtime==0)
manleave(&windows[j]); //客户离开
}
}
printf("\n----------------------------------------------------------------------------\n");
}
void serving() { //服务区显示
printf("\n\n服务区:\n");
printf("----------------------------------------------------------------------------\n");
printf("窗口①:");
struct person *searchp[4];
searchp[0]=windows[0].next;
while(searchp[0]!=NULL) {
printf("[♀·%d]",searchp[0]->number);
searchp[0]=searchp[0]->next;
}
printf("\n----------------------------------------------------------------------------\n");
printf("窗口②:");
searchp[1]=windows[1].next;
while(searchp[1]!=NULL) {
printf("[♀·%d]",searchp[1]->number);
searchp[1]=searchp[1]->next;
}
printf("\n----------------------------------------------------------------------------\n");
printf("窗口③:");
searchp[2]=windows[2].next;
while(searchp[2]!=NULL) {
printf("[♀·%d]",searchp[2]->number);
searchp[2]=searchp[2]->next;
}
printf("\n----------------------------------------------------------------------------\n");
printf("窗口④:");
searchp[3]=windows[3].next;
while(searchp[3]!=NULL) {
printf("[♀·%d]",searchp[3]->number);
searchp[3]=searchp[3]->next;
}
printf("\n----------------------------------------------------------------------------\n");
}
//显示区
void showarrive(struct person *p,int n) {
FILE *fp;
fp=fopen("银行当天数据.txt","a");
printf("客户[%d]号挂号成功",p->number);
fprintf(fp,"客户[%d]号挂号成功!",p->number);
printf(" 到达银行时间为 %d:%d",p->waitting.hour,p->waitting.minute);
fprintf(fp," 到达银行时间为 %d:%d",p->waitting.hour,p->waitting.minute);
printf(" 已在%d号窗口",n+1);
fprintf(fp," 已在%d号窗口",n+1);
printf(" 预计处理时间为:%d分钟\n",p->dealtime);
fprintf(fp," 预计处理时间为:%d分钟\n",p->dealtime);
fclose(fp);
}
void showleave(struct person *p,int n) {
FILE *fp;
fp=fopen("银行当天数据.txt","a");
printf("客户[%d]号办理完成!",p->number);
fprintf(fp,"客户[%d]号办理完成!",p->number);
printf(" 离开银行时间为 %d:%d\n",syshour,sysminute);
fprintf(fp," 离开银行时间为 %d:%d\n",syshour,sysminute);
fclose(fp);
}
2、Java版本
package com.example.main;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.JButton;
import java.awt.Font;
import javax.swing.JTextArea;
import javax.swing.JLabel;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.awt.event.ActionEvent;
public class BankFrame extends JFrame implements ActionListener {
static JPanel contentPane;
static JButton button; // 开始
static JButton button_1;// 暂停
static JScrollPane scrollPane; // 滑动条
static JScrollPane scrollPane_1;
static JTextArea textArea; // 显示框
static JTextArea textArea_1;
static DefaultTableModel tableModel;// TableModel 的一个实现,它使用一个 Vector 来存储单元格的值对象
static JTable table;// 表格
private DefaultTableCellRenderer renderer; // 单元格渲染器
static JScrollPane tableScrollPane;
static boolean go = false;
static List<WindowsThread> windows = new ArrayList<>();
private String[] title = { "窗口名称", "执勤人", "等待人数", "已处理" };
final static int windownumber = 4; // 窗口个数
private Thread producerThread;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
BankFrame frame = new BankFrame();
frame.Process();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public BankFrame() {
setTitle("银行排队系统");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(500, 200, 1000, 600);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
button = new JButton("开始");
button.setFont(new Font("微软雅黑", Font.PLAIN, 15));
button.setBounds(350, 13, 113, 27);
button.addActionListener(this);
contentPane.add(button);
button_1 = new JButton("\u6682\u505C");
button_1.setFont(new Font("微软雅黑", Font.PLAIN, 15));
button_1.setBounds(510, 13, 113, 27);
button_1.addActionListener(this);
contentPane.add(button_1);
scrollPane = new JScrollPane();
scrollPane.setBounds(14, 70, 450, 220);
contentPane.add(scrollPane);
// 文本显示区域
textArea = new JTextArea();
textArea.setFont(new Font("微软雅黑", Font.PLAIN, 16));
scrollPane.setViewportView(textArea);
scrollPane_1 = new JScrollPane();
scrollPane_1.setBounds(510, 70, 450, 220);
contentPane.add(scrollPane_1);
textArea_1 = new JTextArea();
textArea_1.setFont(new Font("微软雅黑", Font.PLAIN, 16));
scrollPane_1.setViewportView(textArea_1);
JLabel label_4 = new JLabel("\u5BA2\u6237\u4FE1\u606F\u663E\u793A\u533A");
label_4.setFont(new Font("微软雅黑", Font.PLAIN, 15));
label_4.setBounds(180, 45, 110, 20);
contentPane.add(label_4);
JLabel label_5 = new JLabel("\u7A97\u53E3\u4FE1\u606F\u663E\u793A\u533A");
label_5.setFont(new Font("微软雅黑", Font.PLAIN, 15));
label_5.setBounds(700, 45, 110, 20);
contentPane.add(label_5);
tableModel = new DefaultTableModel(title, 0);
table = new JTable();
table.setModel(tableModel);
table.setRowHeight(40);
table.setFont(new Font("微软雅黑", Font.PLAIN, 15));
renderer = new DefaultTableCellRenderer();
renderer.setHorizontalAlignment(JLabel.CENTER);
table.setDefaultRenderer(Object.class, renderer);
tableScrollPane = new JScrollPane(table);
tableScrollPane.setBounds(14, 334, 946, 185);
contentPane.add(tableScrollPane);
this.updateTableData();// 跟新表格数据
}
public void Process() {
// 创建四个窗口线程
for (int i = 0; i < windownumber; i++) {
WindowsThread window = new WindowsThread(); // 生成一个窗口线程
BankFrame.windows.add(window); // 把这个线程添加入窗口管理的顺序表
new Thread(window).start(); // 线程启动
}
}
public void updateTableData() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
tableModel.setRowCount(0);
table.setModel(tableModel);
for (WindowsThread win : windows) {
tableModel.addRow(new String[] { win.getWindowname(), win.getPersonname(),
String.valueOf(win.size()), String.valueOf(win.getHistoryCustomerSize()) });
}
}
}, 0, 500);
}
@SuppressWarnings("deprecation")
public void actionPerformed(ActionEvent e) {
if (e.getSource() == button) {
if (producerThread != null) {
JOptionPane.showMessageDialog(null, "客户生成线程已经启动!");
return;
}
producerThread = new Thread(new CustomerComing());
producerThread.start();
} else if (e.getSource() == button_1) {
if (producerThread == null) {
JOptionPane.showMessageDialog(null, "客户生成线程已经停止!");
return;
}
producerThread.stop();
producerThread = null;
}
}
}
package com.example.main;
public class Customer {
private static int count = 1;
private int id;// 客户编号
private long createtime;// 创建时间
private long needtime;// 服务所需时间
private long servicetime;// 服务开始时间
private long endtime;// 服务结束时间
public Customer() {
id = count++;
createtime = System.currentTimeMillis();
this.needtime = (long) (Math.random() * 10000);//10s以内
}
/**
* 显示客户到达信息
*/
public String service() throws InterruptedException {
servicetime = System.currentTimeMillis();
endtime = servicetime + needtime;
Thread.sleep(needtime);
return String.format("[%d]号客户,开始时间:[%s]\n", id, TimeFormat.format(servicetime));
}
/**
* 显示客户离开信息
*/
public String info() {
return String.format("[%d]号客户办理完成,离开时间为:[%s]\n", id, TimeFormat.format(endtime));
}
public synchronized long getId() {
return id;
}
public synchronized long getCreatetime() {
return createtime;
}
public synchronized long getNeedtime() {
return needtime;
}
}
package com.example.main;
public class CustomerComing implements Runnable {
final int maxnumber = 100;
public void run() {
for (int i = 0; i < maxnumber; i++) {
try {
Thread.sleep(1000); //默认每秒钟生成一个客户
} catch (InterruptedException e) {
e.printStackTrace();
}
Customer customer = new Customer(); // 生成一个客户
// 判断哪个窗口的人数最少
int min = Integer.MAX_VALUE;
int minIndex = -1;
for (int j = 0; j < BankFrame.windows.size(); j++) {
int size = BankFrame.windows.get(j).size();
if (size < min) {
min = size;
minIndex = j;
}
}
WindowsThread window = BankFrame.windows.get(minIndex); // 生成一个窗口等于最小人数窗口
window.add(customer);
BankFrame.textArea.append("[" + customer.getId() + "]" + "号客户于["
+ TimeFormat.format(customer.getCreatetime()) + "]到达银行,正在[" + window.getWindowsId() + "]号窗口排队\n");
BankFrame.textArea.append("预计处理时间为" + TimeFormat.format_m(customer.getNeedtime()) + "秒\n\n");
BankFrame.textArea.setCaretPosition(BankFrame.textArea.getText().length());
}
}
}
package com.example.main;
import java.text.SimpleDateFormat;
public class TimeFormat {
private static SimpleDateFormat hmm = new SimpleDateFormat("HH:mm:ss");// 时分秒格式
private static SimpleDateFormat m = new SimpleDateFormat("ss");// 秒
public static String format(long time) {
return hmm.format(time);
}
public static String format_m(long time) {
return m.format(time);
}
}
package com.example.main;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class WindowsThread implements Runnable {
private static int count = 1;
private int windowsId; // 窗口编号
private String windowname; //窗口名称
private String personname; //工作人员名称
private Queue<Customer> window; // 存放客户的窗口队列
private List<Customer> historyCustomer = new ArrayList<Customer>();//存放处理过的客户
public WindowsThread() {
windowsId = count;
windowname = new String("窗口"+count);
personname = new String("工作人员"+count);
window = new LinkedList<Customer>();
count++;
}
public void run() {
while (true) {
synchronized (this) {
while (this.size() == 0) { // 无人生成,窗口休眠
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
try {
historyCustomer.add(window.peek());
BankFrame.textArea_1.append("【" + windowsId + "】号窗口正在服务" + window.peek().service() + "\n");
// peek():获取队列的头,但不移除
BankFrame.textArea_1.append("【" + windowsId + "】号窗口的" + window.poll().info() + "\n");
// poll():获取队列的头并移除
BankFrame.textArea_1.setCaretPosition(BankFrame.textArea_1.getText().length());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getWindowsId() {
return windowsId;
}
public String getWindowname() {
return windowname;
}
public String getPersonname() {
return personname;
}
public synchronized int size() {//避免出现异常
return window.size();
}
public synchronized int getHistoryCustomerSize() {
return historyCustomer.size();
}
public synchronized void add(Customer customer) {
window.add(customer);
notifyAll();
}
}
四、课设总结
通过这一次课程实践,我获益良多,明白了很多之前不明白的,注意到了很多之前没注意的东西。虽然这个程序比较短,可短而精,特别是对于链表、队列、多线程的运用尤其灵活。另外,我注意了很多细节,从中学到了很多。总的来说包括以下几点:
1.对于错误信息的处理问题。本程序有打开文件、输入判断等运用,如果对错误信息不加以处理,将会导致程序运行时使用户不清楚或感到程序忽然死亡的情况。对这一细节加以处理后不仅程序美观,条理清晰,还对用户的使用提供了方便,容易上手。
2.注释应尽量详细。详细的注释不仅能够让看的人清晰明了,对编程者本人的思路整理有好处,使得在编程的过程中不易产生混乱。
3.编程时注意对程序的优化。虽然计算机的处理速度很快,可是当处理的信息一旦太多时,计算机容易反应不过来。为了避免这种情况的发生,程序应当简洁、省时省空间,力求在时间复杂度和空间复杂度之上有所优势,节约资源。
4.思路是编程的核心,在编程之前一定要理清思路,这样编程时才会事半工倍。如果在编程是遇到了瓶颈,应当静下心来重新整理思路,并寻找是否思路上有所纰漏。
5.在编程检验时,应当细心。很多小错误都是由于编程人员的疏忽所造成的,而这些小错误往往不容易发现,对程序的运行也不会造成错误,可是对结果却有重大的影响,使得输出结果不如人意。
总的来说,编程就是熟能生巧,只要多编程,就会发现很多,学到很多。只要在每一次的编程中多注意,多思考,编程能力就会慢慢提高。在大学的日子里,编程的机会不算少,也不算多,为了锻炼能力,更应该重视每一次的编程实践,重要的是从中学到了多少,对你有多大帮助。把握课程实践的机会,将会对个人能力的提高有很多好处。