【Java Swing/散列表】散列表实现电话号码查找系统——问题汇总分享

说明:本文为我在做课设时遇到的问题汇总,解决方法多数来自网络,因深感到处搜索解决方法不易,所以做成汇总。因搜索时没有记录每个答案来源网站,如有侵权,请联系我。感谢无私分享的每一个人。

题目:设计散列表实现电话号码查找系统

基本要求:

(1)设每个记录有下列数据项:电话号码、用户名、地址;

(2)从键盘输入各记录,分别以电话号码和用户名为关键字建立散列表;

(3)查找并显示给定电话号码的记录;

(4)查找并显示给定用户名的记录。

(5)尝试不同类型处理冲突的方法,考察平均查找长度的变化。

目录

界面设计:

Q1: 如何为界面添加背景?将左上角Java图标改为自定义logo?

Q2:如何根据xy坐标像素位置自定义组件布局?

Q3:如何让窗口在电脑屏幕居中?

Q4:将鼠标变成小手的代码?

Q5:如何添加背景音乐?

Q6:如何构建表格并使表格能够实时获取散列表更新内容并刷新?

Q7:如何右击表格某行删除?

Q8:切换界面时,如何对上一个界面中的散列表进行操作?

Q9:为什么文本框输入值比较会出现问题?

Q10:如何在删除或查找成功后显示自动关闭的提示弹窗?

算法设计

Q1:除留取余法和除数的获取?

Q2:扩容条件?如何扩容?

Q3:解决冲突的方法有哪些?

Q4:如何知道删除成功还是失败?

Q5:其他问题?


界面设计:

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:可以私信我要整个程序代码,仅供学习使用~也可以有问题与我交流,如果我会的话一定会解答。内容比较简单,方法也非最优,如有问题欢迎指教,一起学习交流,希望对你有帮助!

其他效果:

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值