说明:本文为我在做课设时遇到的问题汇总,解决方法多数来自网络,因深感到处搜索解决方法不易,所以做成汇总。因搜索时没有记录每个答案来源网站,如有侵权,请联系我。感谢无私分享的每一个人。
题目:设计散列表实现电话号码查找系统
基本要求:
(1)设每个记录有下列数据项:电话号码、用户名、地址;
(2)从键盘输入各记录,分别以电话号码和用户名为关键字建立散列表;
(3)查找并显示给定电话号码的记录;
(4)查找并显示给定用户名的记录。
(5)尝试不同类型处理冲突的方法,考察平均查找长度的变化。
目录
Q1: 如何为界面添加背景?将左上角Java图标改为自定义logo?
Q6:如何构建表格并使表格能够实时获取散列表更新内容并刷新?
界面设计:
Q1: 如何为界面添加背景?将左上角Java图标改为自定义logo?
A1:在网络搜索到以下代码,可建立如下bpic框架类,框架中参数可按需修改。
public class bpic extends JFrame {
int width,height;
String p1,p2;
JPanel contentPane;
Image im;
public bpic(String title,String p1,String p2) {
super(title);
setResizable(false);//设置窗口为不可缩放
setVisible(true);//设置为窗口可见
setDefaultCloseOperation(3);//设置用户在此窗体上发起 "close" 时默认执行的操作。
contentPane = new JPanel(); //指定容器
setContentPane(contentPane);//设置 contentPane 属性
contentPane.setOpaque(false);//设置面板背景为透明(这一步很重要)
init(p1,p2);
}
public void init(String p1,String p2) {
/*
* 设置窗口图标
*/
ImageIcon ig = new ImageIcon(p1);//这里放上你要设置图标图片
im = ig.getImage();
setIconImage(im);
/*
* 设置窗口背景图片
*/
ImageIcon img = new ImageIcon(p2);//要设置的背景图片
JLabel imgLabel = new JLabel(img);//将背景图放在标签里。
this.getLayeredPane().add(imgLabel, new Integer(Integer.MIN_VALUE));//将背景标签添加到jframe的LayeredPane面板里。
imgLabel.setBounds(0, 0, img.getIconWidth(), img.getIconHeight());
}
}
调用代码:
bpic frame = new bpic("电话号码查询系统", //窗口标题
"D:\\Projects\\teleSearch\\图片\\logo.png", //logo图片地址
"D:\\Projects\\teleSearch\\图片\\首页.jpg"); //背景图片地址
效果展示:
Q2:如何根据xy坐标像素位置自定义组件布局?
A2:需要先将窗口frame布局设置为空
frame.setLayout(null); //布局方式设为空,可以根据像素坐标自由确定组件布局
组件再调用setBounds即可
picStart.setBounds(380, 50, 330, 330);
frame.add(picStart);
//对picStart进行了一系列其他设置代码之后
//一定要repaint(),不然不显示组件
picStart.repaint();
Q3:如何让窗口在电脑屏幕居中?
A3:
//设置居中
Toolkit tk = frame.getToolkit();
Dimension screenSize = tk.getScreenSize();
frame.setBounds((screenSize.width - 1100) / 2, (screenSize.height - 800) / 2,
1100, 800);
Q4:将鼠标变成小手的代码?
A4:
picStart.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
Q5:如何添加背景音乐?
A5:在网络搜索到如下代码
/**
* Description: 简易音频播放器(只支持AU,RA,WAV)
* 在不使用JMF的情况下快速实现音频播放
*/
import javax.sound.sampled.*;
import java.io.*;
import java.util.concurrent.TimeUnit;
public class Sound {
private String musicPath; //音频文件
private volatile boolean run = true; //记录音频是否播放
private Thread mainThread; //播放音频的任务线程
private AudioInputStream audioStream;
private AudioFormat audioFormat;
private SourceDataLine sourceDataLine;
public Sound(String musicPath) {
this.musicPath = musicPath;
prefetch();
}
//数据准备
private void prefetch(){
try{
//获取音频输入流
audioStream = AudioSystem.getAudioInputStream(new File(musicPath));
//获取音频的编码对象
audioFormat = audioStream.getFormat();
//包装音频信息
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class,
audioFormat,AudioSystem.NOT_SPECIFIED);
//使用包装音频信息后的Info类创建源数据行,充当混频器的源
sourceDataLine = (SourceDataLine)AudioSystem.getLine(dataLineInfo);
sourceDataLine.open(audioFormat);
sourceDataLine.start();
}catch(UnsupportedAudioFileException ex){
ex.printStackTrace();
}catch(LineUnavailableException ex){
ex.printStackTrace();
}catch(IOException ex){
ex.printStackTrace();
}
}
//析构函数:关闭音频读取流和数据行
protected void finalize() throws Throwable{
super.finalize();
sourceDataLine.drain();
sourceDataLine.close();
audioStream.close();
}
//播放音频:通过loop参数设置是否循环播放
private void playMusic(boolean loop)throws InterruptedException {
try{
if(loop){
while(true){
playMusic();
}
}else{
playMusic();
//清空数据行并关闭
sourceDataLine.drain();
sourceDataLine.close();
audioStream.close();
}
}catch(IOException ex){
ex.printStackTrace();
}
}
private void playMusic(){
try{
synchronized(this){
run = true;
}
//通过数据行读取音频数据流,发送到混音器;
//数据流传输过程:AudioInputStream -> SourceDataLine;
audioStream = AudioSystem.getAudioInputStream(new File(musicPath));
int count;
byte tempBuff[] = new byte[1024];
while((count = audioStream.read(tempBuff,0,tempBuff.length)) != -1){
synchronized(this){
while(!run)
wait();
}
sourceDataLine.write(tempBuff,0,count);
}
}catch(UnsupportedAudioFileException ex){
ex.printStackTrace();
}catch(IOException ex){
ex.printStackTrace();
}catch(InterruptedException ex){
ex.printStackTrace();
}
}
//暂停播放音频
private void stopMusic(){
synchronized(this){
run = false;
notifyAll();
}
}
//继续播放音乐
private void continueMusic(){
synchronized(this){
run = true;
notifyAll();
}
}
//外部调用控制方法:生成音频主线程;
public void start(boolean loop){
mainThread = new Thread(new Runnable(){
public void run(){
try {
playMusic(loop);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
mainThread.start();
}
//外部调用控制方法:暂停音频线程
public void stop(){
new Thread(new Runnable(){
public void run(){
stopMusic();
}
}).start();
}
//外部调用控制方法:继续音频线程
public void continues(){
new Thread(new Runnable(){
public void run(){
continueMusic();
}
}).start();
}
}
调用代码:
Sound player = new Sound("D:/Projects/teleSearch/Canon.wav"); //音频地址
player.start(true); //调用start方法,开始线程
//因为我的背景音乐是开机bgm,切换界面后将不再进行
//故在鼠标监听器点击strat图标的代码段中,要终止运行
//终止代码
player.stop();
Q6:如何构建表格并使表格能够实时获取散列表更新内容并刷新?
A6:在网络学习后,建立如下适合本系统的表格
//定义全局变量,便于在鼠标监听器中进行刷新修改
private DefaultTableModel model1,model2;
//全局变量table,并且设置table为不可修改(但可以选中)
private JTable table = new JTable(){
public boolean isCellEditable(int rowIndex, int ColIndex){
return false;
}
};
//全局变量,线程计时时间
//用于线程计时
final long timeInterval = 2000;
//表格初步建立
//设置表格标题
String[] header1 = {"序号","用户名", "电话号码","地址"};
String[] header2 = {"序号","电话号码", "用户名","地址"};
//这里data为空,因为主要内容的获取在鼠标监听获取事件后进行
Object[][] data = {};
// create a table model
model1 = new DefaultTableModel(data, header1);
model2 = new DefaultTableModel(data, header2);
// create a JTable object
table.setModel(model1);
table.getTableHeader().setReorderingAllowed(false); //不可整列移动
table.getTableHeader().setResizingAllowed(false); //不可拉动表格
//内容设置居中
DefaultTableCellRenderer r = new DefaultTableCellRenderer();
r.setHorizontalAlignment(JLabel.CENTER);
table.setDefaultRenderer(Object.class,r);
//设置表格风格
table.setGridColor(Color.BLACK);
table.getTableHeader().setFont(font2);
table.setFont(font1);
//设置行高
table.setRowHeight(40);
//设置列宽
table.getColumnModel().getColumn(0).setPreferredWidth(70);
table.getColumnModel().getColumn(1).setPreferredWidth(160);
table.getColumnModel().getColumn(2).setPreferredWidth(160);
table.getColumnModel().getColumn(3).setPreferredWidth(600);
// must do: bind a JTable object to a JScrollPane object, otherwise the header of the table will not be displayed in the JPanel.
JScrollPane jScrollPane = new JScrollPane();
// key, crucial!
jScrollPane.setViewportView(table);
jScrollPane.setBounds(60, 85, 950, 600);
jScrollPane.repaint();
frame.add(jScrollPane);
//获取hash表内容并更新至表格
//以下内容在鼠标监听中
//加入多线程计时器
Runnable runnable1 = new Runnable() {
public void run() {
while (isName) { //一般的线程这里是true,因为本系统有用户名和电话号码两种操作选
项,所以需要进行判断,否则两套线程一起运行会出错,具体情况具体分析
// 要运行的任务代码
//建立一个数组,接收hash表中内容
//可以在hash表中建立一个返回值为数组的方法,在此不多做解释
//例如这个数组
String[][] dataName1 = new String[lenName1][4];
//设置给model1(model2同理)
model1 = new DefaultTableModel(dataName1,header1);
//改变表格内容
table.setModel(model1);
//设置行高
table.setRowHeight(40);
//设置列宽
table.getColumnModel().getColumn(0).setPreferredWidth(70);
table.getColumnModel().getColumn(1).setPreferredWidth(160);
table.getColumnModel().getColumn(2).setPreferredWidth(160);
table.getColumnModel().getColumn(3).setPreferredWidth(600);
table.repaint();
// ------- ends here
try {
// sleep():同步延迟数据,并且会阻塞线程
Thread.sleep(timeInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//创建定时器
Thread thread = new Thread(runnable1);
//开始执行
thread.start();
效果:
Q7:如何右击表格某行删除?
A7:构建如下方法并调用
由于表格每一秒刷新一次,刷新时若尝试获取表格内容会报错,此错误未解决,但不影响使用,只要在一秒内点击删除就可以啦
//创建一个JPopupMenu
private void createPopupMenu() {
m_popupMenu = new JPopupMenu();
JMenuItem delMenItem = new JMenuItem();
delMenItem.setText(" 删除 ");
delMenItem.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
//该操作需要做的事
int row = table.getSelectedRow();
//获取所选中行的数值
//获取某行列元素值的方法为
//table.getValueAt(row,1).toString();
//row为所选中行,1为第二列(从0开始计算)
//获取值之后,作为参数传入,调用删除方法即可
}
});
m_popupMenu.add(delMenItem);
}
private void tableMouseClicked(java.awt.event.MouseEvent evt) {
mouseRightButtonClick(evt);
}
//鼠标右键点击事件
private void mouseRightButtonClick(java.awt.event.MouseEvent evt) {
//判断是否为鼠标的BUTTON3按钮,BUTTON3为鼠标右键
if (evt.getButton() == java.awt.event.MouseEvent.BUTTON3) {
//通过点击位置找到点击为表格中的行
int focusedRowIndex = table.rowAtPoint(evt.getPoint());
if (focusedRowIndex == -1) {
return;
}
//将表格所选项设为当前右键点击的行
table.setRowSelectionInterval(focusedRowIndex, focusedRowIndex);
//弹出菜单
m_popupMenu.show(table, evt.getX(), evt.getY());
}
}
//调用方法,加入在建立界面的方法中
//右键删除
createPopupMenu();
table.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
tableMouseClicked(evt);
}
});
Q8:切换界面时,如何对上一个界面中的散列表进行操作?
A8:将散列表作为全局变量,并作为参数传入
Q9:为什么文本框输入值比较会出现问题?
A9:textSearch.getText() == "aaa"是无法比较的,字符串的比较需要用textSearch.getText() .equals("aaa")
Q10:如何在删除或查找成功后显示自动关闭的提示弹窗?
A10:
先写一个悬浮窗口
//一个悬浮窗口
class Fail extends JDialog
{
public Fail ()
{
Font font2 = new Font("宋体",Font.BOLD,25);
setUndecorated (true);
setBackground(Color.white);
setSize (200, 70);
setLocationRelativeTo (null);
setLayout (new BorderLayout ());
JLabel label =new JLabel ("用户不存在:(");
label.setFont(font2);
label.setHorizontalAlignment (JLabel.CENTER);
add (label,BorderLayout.CENTER);
}
}
在删除失败的条件下调用显示
//显示删除失败弹窗
final Fail ff = new Fail();
ff.setVisible (true);
// 在swing中调用线程,必须如下:
SwingUtilities.invokeLater (new Runnable()
{
@Override
public void run ()
{
try
{
Thread.sleep (1000);
}
catch (InterruptedException e1)
{
e1.printStackTrace();
}
ff.dispose ();
}
});
}
//自动关闭窗口
windowClear.dispose();
算法设计
Q1:除留取余法和除数的获取?
A1:
//除留取余法,获取哈希码
public int getHashCode(String name){
int sum = 0;
for(int i = 0; i < name.length(); i++){
char ch = name.charAt(i);
sum += Integer.valueOf(ch);
}//将字符串转化为int类型,计算关键字name的值
sum = sum % m; //除留取余,找到key值在散列表中对应的位置
return sum;
}
//判断素数
public static boolean isprime(int n) {
if (n < 2)
return false;
if(n == 2)
return true;
if(n % 2 == 0)
return false;
for (int i = 3; i < n; i += 2)
if (n % i == 0)
return false;
return true;
}
//确定除留取余法的除数:小于size的最大素数
public static int confirm(int m){
while(!isprime(m)){//如果函数返回值是false,说明m不是素数,就把m--
--m;
}
return m;
}
Q2:扩容条件?如何扩容?
A2:定义装载因子,大小为散列表已有元素个数/散列表大小(记得转成double),当装载因子>=0.7时,进行扩容,扩容代码如下(自己写的,比较麻烦,如有更好的扩容方法欢迎指教)
public void expandName(){
aslCount1 = 0;
int n = dataNum;
DataItem[] preArray = new DataItem[dataNum];
preArray = getNameArray();
arraySize *= 2;
m = confirm(arraySize);
dataNum = 0;
dataArray = new DataItem[m];
for(int i = 0;i < n;i++){
insertNameData(preArray[i].getName(),preArray[i].getNum(),preArray[i].getAddress());
}
//System.out.println("扩容成功");
}
Q3:解决冲突的方法有哪些?
A3:我采用了线性探测、二次探测、再哈希法、链表法四种方法,给出四种方法的插入操作,其他操作同理。
线性探测
若当前位置产生冲突,则当前位置+1,重复操作,直到不产生冲突为止。
//插入hash表 #用户名
public void insertNameData(String name, String num, String address){
int count = 0;//记录冲突次数
DataItem data = new DataItem(name,num,address);
int hashCode = getHashCode(name);
aslCount1++;
//用于计算查找成功平均查找长度,将每个元素插入时的探测次数记录并累加,若删除则减去
while (dataArray[hashCode] != null){
//向后探测
if(hashCode < m - 1)
hashCode++;
else
hashCode = 0;
count++;
aslCount1++;
if(car >= 0.7){
//给散列表扩容
expandName();
insertNameData(name, num, address);
return;
}
}
if(count != m){
dataArray[hashCode] = data;
dataNum++;
car = (double)dataNum / (double)m;
// System.out.println(car);
// System.out.println("添加成功 "+data.getName()+data.getNum()+data.getAddress());
// System.out.println("冲突次数" + count);
// System.out.println("当前数据个数" + dataNum);
}
}
二次探测
若当前位置产生冲突,则hashCode为当前位置的+ - 1,2,3,….的平方处,逐个判断直到没有冲突为止。
//插入hash表 #用户名
public void insertNameData(String name, String num, String address){
int stepSize, i = 1;
DataItem data = new DataItem(name,num,address);
int hashCode = getHashCode(name);
if(dataArray[hashCode] == null){
dataArray[hashCode] = data;
dataNum++;
aslCount1++;
car = (double)dataNum / (double)m;
//System.out.println("添加成功 "+data.getName()+data.getNum()+data.getAddress());
//System.out.println("当前数据个数" + dataNum);
return;
}
while (i <= m/2){
aslCount1++;
stepSize = i * i;
hashCode += stepSize;
hashCode %= m;
if(dataArray[hashCode] == null)
break;
hashCode -= stepSize;
if(hashCode < 0){
hashCode = 0 - hashCode;//变为正号
hashCode %= m;
hashCode = m - hashCode;
}
if(dataArray[hashCode] == null)
break;
i++;
hashCode = getHashCode(name);
}
if(dataArray[hashCode] == null){
dataArray[hashCode] = data;
dataNum++;
//System.out.println("添加成功 "+data.getName()+data.getNum()+data.getAddress());
//System.out.println("当前数据个数" + dataNum);
}else {
//进行扩容再次递归添加
//System.out.println("进行扩容");
expandName();
insertNameData(name,num,address);
}
}
再哈希法
构造一个二次哈希函数,当产生冲突时,当前哈希值加上步长stepSize利用除留取余法得到新的哈希值进行判断,直到不产生冲突为止。
//插入hash表 #用户名
public void insertNameData(String name, String num, String address){
DataItem data = new DataItem(name,num,address);
int hashCode = getHashCode(name);
int stepSize = getHashCode2(name);
aslCount1++;
while (dataArray[hashCode] != null){
aslCount1++;
hashCode += stepSize;
hashCode %= m;
if(car >= 0.7){
//给散列表扩容
expandName();
insertNameData(name, num, address);
return;
}
}
dataArray[hashCode] = data;
dataNum++;
car = (double)dataNum / (double)m;
//System.out.println("添加成功 "+data.getName()+data.getNum()+data.getAddress());
//System.out.println("负载因子" + car);
//System.out.println("当前数据个数" + dataNum);
}
//二次哈希函数,不能输出0,不能与第一次哈希函数相同
public int getHashCode2(String name){
int sum = 0,constant; //constant是常数,是质数且小于数组容量
constant = confirm(m - 1);//令constant为小于m(hash表大小)的最大质数
for(int i = 0; i < name.length(); i++){
char ch = name.charAt(i);
sum += Integer.valueOf(ch);
}//将字符串转化为int类型,计算关键字name的值
sum = constant-(sum % constant);//公式
return sum;
}
链表法
计算出哈希码,相同哈希码的元素以链表的形式逐个向后添加,便于插入,但查找和删除时需要在对应哈希码的链表中逐一查找。
hashtable类中方法
//插入 #用户名
public void insertName(String name,String num,String address){
//获取name的哈希码
int hashCode = getHashCode(name);
//System.out.println("哈希码为" + hashCode);
Node newNode = new Node(name,num,address);
//System.out.println("要插入的信息为" + newNode.name + newNode.num + newNode.address);
dataArray[hashCode].insertName(newNode);
dataNum++;
aslCount1 += dataArray[hashCode].getCount();
//System.out.println("插入成功第"+ hashCode+"hash表的长度为"+dataArray[hashCode].getLength() + "当前共有数据" + dataNum);
}
linklist类中方法,需自定义node类
//插入用户 #用户名
public void insertName(Node node){
count = 1;
//二种情况:1.链表为空;2.链表有节点
//1.链表为空
if(head == null){
head = node;
length++;
return;
}
//2.链表存在节点
//定义一个辅助指针,找到链表的最后位置,再进行插入
Node temp = head;
//通过while循环,找到最后
while (temp.next != null){
count++;
temp = temp.next;
}//循环退出时temp即为最后的节点
//将新添的节点赋值于最后节点的下一个
temp.next=node;
length++;
//System.out.println("插入成功,该链表长度为" + length);
}
Q4:如何知道删除成功还是失败?
A4:将删除的方法返回值设置为布尔类型
Q5:其他问题?
A5:可以私信我要整个程序代码,仅供学习使用~也可以有问题与我交流,如果我会的话一定会解答。内容比较简单,方法也非最优,如有问题欢迎指教,一起学习交流,希望对你有帮助!
其他效果: