• [技术干货] Java发送邮箱验证码、session校验功能
    1、引入的jar包是mail-1.4.jar2、java底层发送邮箱方法12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public boolean sendEMail(Map<String, Object> map) {    log.info("電子郵件接口執行開始!");    String from = Cache.getInstance().getParamsConstant.EMAIL_FROM);    String pwd = Cache.getInstance().getParams(Constant.EMAIL_PWD);    String to = String.valueOf(map.get("mail"));    String subject = String.valueOf(map.get("subject"));    String content = String.valueOf(map.get("content"));    String smtpHost = Cache.getInstance().getParams(Constant.EMAIL_SMTPHOST);    if(StringUtils.isBlank(to) || StringUtils.isBlank(subject) || StringUtils.isBlank(content)){      log.info("郵件地址("+to+")、主題("+subject+")、內容("+content+")有為空項,不符合發送條件");      return false;    }    try{       Properties props = new Properties();       props.setProperty("mail.transport.protocol", "smtp"); // 使用的协议(JavaMail规范要求)       props.setProperty("mail.smtp.host", smtpHost); // 发件人的邮箱的 SMTP服务器地址       props.setProperty("mail.smtp.auth", "true"); // 请求认证,参数名称与具体实现有关       // 创建Session实例对象       Session session = Session.getDefaultInstance(props);       // 创建MimeMessage实例对象       MimeMessage message = new MimeMessage(session);       // 设置发件人       message.setFrom(new InternetAddress(from));       // 设置收件人       message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));       // 设置发送日期       message.setSentDate(new Date());       // 设置邮件主题       message.setSubject(subject);       // 设置纯文本内容的邮件正文       message.setText(content);       // 保存并生成最终的邮件内容       message.saveChanges();       // 设置为debug模式, 可以查看详细的发送 log       session.setDebug(true);       // 获取Transport对象       Transport transport = session.getTransport("smtp");       // 第2个参数需要填写的是QQ邮箱的SMTP的授权码,什么是授权码,它又是如何设置?       transport.connect(from, pwd);       // 发送,message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人       transport.sendMessage(message, message.getAllRecipients());       transport.close();    }catch(Exception e){      log.error(e.getMessage(), e);      return false;    }    log.info("電子郵件接口執行完成,執行成功!");    return true;  }3、java业务逻辑层调用发送邮箱、验证码加密存储在session中1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374@RequestMapping("/sendEMail.do")  public void sendEMail(HttpServletRequest request, HttpServletResponse response) {    String json = "{\"success\":false,\"message\":\"操作失败\",\"returnVal\":\"\"}";    boolean res = false;//发送邮件标记     Map<String, Object> map = new HashMap<String, Object>();    try {      String username = request.getParameter("username");      User user = this.userService.findUserByLoginname(username);      if(null == user){        json = "{\"success\":false,\"message\":\"无此人员\",\"returnVal\":\"\"}";        return;      }      //获取当前绑定邮箱地址      String mail = user.getEmail();      //验证邮箱地址      if(StringUtils.isBlank(mail) || "null".equals(mail)){        json = "{\"success\":false,\"message\":\"当前用户尚未绑定邮箱\",\"returnVal\":\"\"}";        return;      }      //生成验证码      String checkcode = Common.getCode();      //发送验证码      String _debug = Cache.getInstance().getParams(Constant.UIAS_DEBUG);      if("1".equals(_debug)){//调试模式 不真发送 直接回显        map.put("checkcode", checkcode);        res = true;      }else{//非调试模式 真实发送邮件        //验证是否频繁发送验证码请求  单位 秒        String intervalTime = Cache.getInstance().getParams(Constant.EMAIL_INTERVAL_TIME);        Object emailTimeFlag = request.getSession().getAttribute("emailTimeFlag");        boolean checkTime = true;        if (emailTimeFlag != null) {          int miao = (int) (((new Date()).getTime() - (Long) emailTimeFlag) / 1000);          if (miao <= Integer.parseInt(intervalTime)) {            checkTime=false;          }         }         if(checkTime){          //组装数据          String email_validity = Cache.getInstance().getParams(Constant.EMAIL_VALIDITY);          String vartime= String.valueOf((Integer.parseInt(email_validity)/60000));          String content = Cache.getInstance().getParams(Constant.EMAIL_CONTENT);          String subject = Cache.getInstance().getParams(Constant.EMAIL_SUBJECT);          content = content.replaceAll("\\$code", checkcode);          content = content.replaceAll("\\$time",vartime);          map.put("mail", mail);          map.put("subject", subject);          map.put("content", content);          //调用发送          res = ieMailService.sendEMail(map);        }else{          json = "{\"success\":false,\"message\":\"请耐心等待,不要频繁发送验证码申请\",\"returnVal\":\"\"}";          return;        }      }      //发送成功后续操作      if(res){        //把验证时间放在session里        request.getSession().setAttribute("emailTimeFlag", new Date().getTime());        //把验证码放在session里  md5 2次加密验证码        String codeMd5 = DigestUtils.md5Hex((DigestUtils.md5Hex(checkcode).toLowerCase()));        request.getSession().setAttribute("emailCodeFlag", codeMd5);        if("1".equals(_debug)){          json = "{\"success\":true,\"message\":\"邮箱验证码发送成功,已经待填到验证码框!\",\"returnVal\":\"\",\"checkcode\":\""+checkcode+"\"}";        }else{          json = "{\"success\":true,\"message\":\"邮箱验证码发送成功,请查收邮件!\",\"returnVal\":\"\"}";        }      }    } catch (Exception e) {      logger.error("发送邮件验证码异常", e);    }finally{      renderHtml(response, json, "encoding:UTF-8");    }  } 4、java验证邮箱,比对session中的验证码和提交上来的验证码12345678910111213141516171819202122232425262728293031@RequestMapping("/check.do")  public void check(HttpServletRequest request, HttpServletResponse response) {    String json = "{\"success\":false,\"message\":\"操作失败\",\"returnVal\":\"\"}";    try {      //验证码有效期校验      String email_validity = Cache.getInstance().getParams(Constant.EMAIL_VALIDITY);      Object emailTimeFlag = request.getSession().getAttribute("emailTimeFlag");      if (emailTimeFlag != null) {        int miao = (int) (((new Date()).getTime() - (Long) emailTimeFlag) / 1000);        if (miao >= Integer.parseInt(email_validity)) {          json = "{\"success\":false,\"message\":\"验证码已过有效期,请重新获取!\",\"returnVal\":\"\"}";        }else{          //在有效期内 比对验证码准确性          String checkcode = request.getParameter("checkcode");          // md5 2次加密验证码          String codeMd5 = DigestUtils.md5Hex((DigestUtils.md5Hex(checkcode).toLowerCase()));          Object emailCodeFlag = request.getSession().getAttribute("emailCodeFlag");          boolean res = codeMd5.equals(String.valueOf(emailCodeFlag));          if(res){            json = "{\"success\":true,\"message\":\"验证码通过!\",\"returnVal\":\"\"}";          }else{            json = "{\"success\":false,\"message\":\"验证码不匹配,请重新核对!\",\"returnVal\":\"\"}";          }        }      }     } catch (Exception e) {      logger.error("验证码异常", e);    }finally{      renderHtml(response, json, "encoding:UTF-8");    }  }5、小插曲,java处理要展示到页面的邮箱和手机号加*号隐藏的正则表达式1234// 括号表示组,被替换的部分$n表示第n组的内容        mail = mail.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4");// 括号表示组,被替换的部分$n表示第n组的内容        phone = phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");6、HTML 获取验证码按钮                 1<input id="btnMail" type="button" onclick="start_mail_button();" value="获取邮件验证码" />7、js 函数调用  按钮60秒倒计时1234567891011121314151617181920212223242526272829303132333435363738//获取邮箱验证码function start_mail_button(){  var username = $('#username').val().trim();//用戶名  if('' == username){    ErrorMsg("请输入用户名");    return;  }  $.ajax({    url: basePath+'/sendEMail.do',type: 'post',dataType: 'json',    data: {username: username},    success: function(data){      if(data.success){        SuccMsg(data.message);        //页面获取验证码按钮样式处理         $("#btnMail").attr('disabled','disabled');         var count = 1 ;         var sum = 60;         var i = setInterval(function(){          if(count > 60){           $("#btnMail").attr('disabled',false);           $("#btnMail").val("获取邮箱验证码");           clearInterval(i);          }else{           $("#btnMail").val("剩余"+parseInt(sum - count)+'秒');          }          count++;         },1000);         //debug模式待填         if(data.checkcode){           $("#checkcode").val(data.checkcode);         }      }else{        ErrorMsg("获取失败!"+data.message);      }    }  });   }8、提交验证码的js没什么特别的,就不上代码了。
  • [问题求助] 求 mqtt 怎么对接华为云IoT 示例代码(java)
    求 mqtt 怎么对接华为云IoT  示例代码(java)
  • [全栈开发者] 【Java编程创造营】零基础入门,夯实Java基础!免费学习还有奖品拿!
    使用简单、编程简洁,让Java成为常年“霸榜”的编程语言随着5G时代的到来,越来越多的行业也更需要Java程序员,上升空间更大,未来发展前景广阔。华为云推出【Java编程创造营】零基础入门,夯实Java基础免费学习!完成学习任务还可以获得华为云认证!还有更过精彩好礼等你拿! 01.活动介绍&适合人群 ▶课程时间:第一阶段:2021年5月10日-2021年6月30日  》第一阶段最终积分排行榜和最终考核成绩公示《第二阶段:2021年7月05日-2021年8月20日  》第二阶段最终积分排行榜和最终考核成绩公示《第三阶段:2021年9月6日-2021年10月24日  》第三阶段最终积分排行榜和最终考核成绩公示《▶适合人群:高校开发者、想学习Java的零基础开发者▶课程亮点:亮点1:为了让大家拥有更专业的指导,特意邀请了华为金牌讲师金牌讲师授课,丰富开发经验,直播点评+论坛/社群答疑,专家引导少走弯路亮点2:沙箱实验+华为官方微认证加持,学习实践相结合报名课程即可获得沙箱实验&华为云官方微认证机会,官方微认证加持更具权威!亮点3:和好友一起学习,得“豪”豪礼,一起进步还有奖品邀请好友一起报名学习本次课程,就有机会得到实物奖品!一起学习,共享成果!亮点4:现在报名就送码豆,打卡即有无线耳机等实物奖励免费报名!免费报名!免费报名!数量有限,先到先得!现在完成课程报名送100码豆,码豆数量有限先到先得完成对应学习任务,累计排名达标也有奖品哦~ 02.活动参与流程 ▶报名活动&课程列表:- 活动报名入口(报名活动才能参加积分排名):   点此报名- 课程入口:- 阶段一:Java编程基础课程:点此学习- 阶段二:Java面向对象编程:点此学习- 阶段三:Java高级实战编程:点此学习▶加入学习社群:有小助手随时随地更新最新课程/学习动态,有同类别的开发者一起分享学习心得和排忧解惑,更有华为云专家不定时空降和大家聊聊课程最IN进度!你还不来么?扫描下方二维码,回复:Java+姓名,加入学习群吧~ 03.课程安排&学习任务  一、阶段三课程安排:》第三阶段最终积分排行榜和最终考核成绩公示《二、学习任务&附加奖励:序号阶段任务详情活动积分奖励附加奖励提交入口1每章随堂测试任务完成方式:在随堂测试打卡帖中,回复对应章节的随堂    回复格式:华为云ID+截图(露出右上角华为云ID,且有效回复次数≥1)2分 /每有效打卡/每章节在提交随堂测试的用户中,抽取1名幸运奖励华为云定制鼠标*1点击前往2每周学习笔记分享任务完成方式:在学习笔记征集帖中,回复本周课程内容的学习笔记    回复格式:华为云ID+笔记内容(字数≥200字)2分 /每有效打卡/每周在本提交学习笔记的用户中,抽取1名幸运奖励华为云定制鼠标*1敬请期待点击前往3相关文章征集任务完成方式:本学习阶段任意时间内,在“华为云-博客”,发表与学习内容相关的任意原创博客内容,将文章链接回复到指定帖内    回复格式:华为云ID+博客文章链接(字数≥600字)5积分 /每篇有效心得/每阶段 最高10/每阶段每阶段选取1篇最佳博文,奖励机械键盘*1点击前往4问答官排位赛任务完成方式:用户可在相关活动帖中发布自己学习中的疑惑或实践问题,其他用户可参与回答。由专家评审,同一问题下确定一名最佳答案。回复格式:华为云ID+提问的问题/解答答案5积分/每有效问题(最高10分)2积分/每有答复(最高4分)最终会全部每阶段参与互动的用户中,抽取3位幸运奖奖励华为云定制鼠标*1点击前往5完成阶段沙箱实验完成规定的沙箱实验20分/ 沙箱实验/点击前往6完成相关微认证完成规定的微认证并获得微认证证书30分/认证/点击前往03.活动奖励本次活动不仅免费跟大牛学习技术,更有积分大礼等着大家~每次有效提交的学习任务都会得到对应积分,赢取智能硬件,还有华为无线耳机、机械键盘等你拿~(以下奖励获取需满足积分≥30分)点击了解Java编程创造营专属福利 04.活动注意事项 ○所有活动奖品将在活动结束后将进行奖项公式公示后15个工作日内完成发放;○活动奖品颜色、型号随机,且部分奖品数量有限发完即止,,若有缺货或其他情况将替换为同价值的其他奖品;○本次活动回帖内容需满足华为云论坛发帖规范:https://bbs.huaweicloud.com/forum/thread-23077-1-1.html○码豆奖励活动规则:1)码豆可在DevCloud会员中心兑换实物礼品;2)码豆奖励将于活动结束后的3个工作日内充值到账,请到会员中心的“查看明细”中查看到账情况;3)码豆只能用于会员中心的礼品兑换,不得转让,具体规则请到会员中心阅读“码豆规则”;4)为保证码豆成功发放,请先登录一次会员中心。会员中心入口:https://devcloud.huaweicloud.com/bonususer/home○活动期间请勿更改昵称、华为云ID等信息,如出现无法发放码豆的情况视为自愿放弃。
  • [技术干货] Java中对象操作流
    ObjectOutputStream构造方法ObjectOutputStream(OutputStream out)创建一个写入指定的OutputStream的ObjectOutputStream对象成员方法void writeObject(Object obj)将指定的对象写入ObjectOutputStream示例代码import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class ObjectOutputStreamTest1 {    public static void main(String[] args) throws IOException {        User user1 = new User("tangsan","123456");        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io-practice\\user1.txt"));                oos.writeObject(user1);        oos.close();    }}ObjectInputStream构造方法ObjectInputStream(InputStream in)创建从指定的InputStream读取的ObjectInputStream成员方法Object readObject()从ObjectInputStream读取一个对象示例代码import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class ObjectInputStreamTest1 {    public static void main(String[] args) throws IOException, ClassNotFoundException {        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io-practice\\user1.txt"));        //此处ois读取出来的数据是Object类型的,需要强转为User类型        User user1 = (User) ois.readObject();        //System.out.println(user1.toString());        System.out.println(user1);        ois.close();    }}
  • [技术干货] 【LeetCode】07 整数反转(java)
    给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。如果反转后整数超过 32 位的有符号整数的范围[−231, 231− 1] ,就返回 0。假设环境不允许存储 64 位整数(有符号或无符号)。代码:/**     * 07 整数反转     * 给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。     * 如果反转后整数超过 32 位的有符号整数的范围[−231, 231− 1] ,就返回 0。     * 假设环境不允许存储 64 位整数(有符号或无符号)。     * 输入:x = 123     * 输出:321     */    @Test    public void test7(){        System.out.println(reverse(1534236469));    }    public int reverse(int x) {        //注意捕获异常        if(x>0){            StringBuffer s=new StringBuffer(String.valueOf(x));            s = s.reverse();            try {                return Integer.parseInt(String.valueOf(s));            } catch (NumberFormatException e) {                return 0;            }        }        if(x<0){            StringBuffer s=new StringBuffer(String.valueOf((0-x)));            s =s.reverse();            try {                return (0-Integer.parseInt(String.valueOf(s)));            } catch (NumberFormatException e) {                return 0;            }        }        return x;    }
  • [技术干货] java之 io流
                               第10课 I/O流================================================简介:       流是程序与外部进行数据交换的通道。       输入流,程序从程序外部读取数据的通道,流的指向称为源。                       源可以是文件、输入设备、网络端口、一块内存。       输出流,程序把程序中的数据写到外部的通道,流的指向称为目的地。                        目的地可以是文件、输出设备、网络端口、一块内存。       通道中传输数据的基本单位:字节、字符。        字节流,通道中传输数据的基本单位是字节。        字符流,通道中传输数据的基本单位是字符。        流相关的接口、类都在java.io包中。------------------------------------------------------------------------------流分类-------------------------------------------------------------------------------------字节流        字节输入流  InputStream                     public abstract int read() throws java.io.IOException;                     public int read(byte[]) throws java.io.IOException;                     public int read(byte[], int, int) throws java.io.IOException;                     public void close() throws java.io.IOException;         字节输出流 OutputStream                     public abstract void write(int) throws java.io.IOException;                     public void write(byte[]) throws java.io.IOException;                     public void write(byte[], int, int) throws java.io.IOException;                     public void flush() throws java.io.IOException;                     public void close() throws java.io.IOException;字符流        字符输入流  Reader                   public int read() throws java.io.IOException;                   public int read(char[]) throws java.io.IOException;                   public abstract int read(char[], int, int) throws java.io.IOException;                   public long skip(long) throws java.io.IOException;                   public abstract void close() throws java.io.IOException;       字符输出流 Writer                  public void write(int) throws java.io.IOException;                  public void write(char[]) throws java.io.IOException;                  public abstract void write(char[], int, int) throws java.io.IOException;                  public abstract void flush() throws java.io.IOException;                  public abstract void close() throws java.io.IOException;具体的流四上面4个流的子类或子孙类。-------------------------------------------------------------------------------------File类=============================================简介:        File类描述了文件、文件夹。        File类没有对文件的读写方法。-------------------------------------------------------------------------------构造方法  public java.io.File(java.lang.String pathname);          public java.io.File(java.lang.String dir, java.lang.String filename);  public java.io.File(java.io.File dir, java.lang.String filename);------------------------------------------------------------------------实例方法:public String getName() 获取文件的名字。public boolean canRead() 判断文件是否是可读的。public boolean canWrite() 判断文件是否可被写入。public boolean exits() 判断文件是否存在。public long length() 获取文件的长度(单位是字节)。public String getAbsolutePath() 获取文件的绝对路径。public String getParent() 获取文件的父目录。public boolean isFile() 判断文件是否是一个普通文件,而不是目录。public boolean isDirectroy() 判断文件是否是一个目录。public boolean isHidden() 判断文件是否是隐藏文件。public long lastModified() 获取文件最后修改的时间。  public java.lang.String[] list();  public java.lang.String[] list(java.io.FilenameFilter);  public java.io.File[] listFiles();  public java.io.File[] listFiles(java.io.FilenameFilter);  public java.io.File[] listFiles(java.io.FileFilter);-------------过滤器接口------------------public interface java.io.FileFilter {  public abstract boolean accept(java.io.File);}public interface java.io.FilenameFilter {  public abstract boolean accept(java.io.File, java.lang.String);}----------------------------------------------------------------------------------------------文件流============================================文件字节流     FileInputStream    文件字节流可以调用从父类继承的read方法顺序地读取文件,只要不关闭流,每次调用read方法就顺序地读取文件中的其余内容,直到文件的末尾或文件字节输入流被关闭。int read() 读取单个字节的数据,返回字节值(0~255整数),如果未读出字节就返回-1。int read(byte b[]) 读取b.length个字节到字节数组b中,返回实际读取的字节数。如果到达文件的末尾,则返回-1。int read(byte b[], int off, int len) 读取len个字节到字节数组b中,并返回实际读取的字节数目。如果到达文件的末尾,则返回-1,参数off指定从字节数组的某个位置开始存放读取的数据。        FileOutputStream   输出流的wirie方法以字节单位向目的地写数据。FileOutputStream流顺序地写文件,只要不关闭流,每次调用write方法就顺序地向目的地写入内容,直到流被关闭。    void write(int n) 向目的地写入单个字节。   void write(byte b[]) 向目的地写入一个字节数组。   void write(byte b[],int off,int len) 从字节数组中偏移量off处取len个字节写到目的地。文件字符流    FileReader    FileWriter缓冲流=============================================缓冲字符流BufferedReaderBufferedWriter**掌握流的套接文件随机读写流=============================================使用RandomAccessFile类来创建一个随机访问文件流。RandomAccessFile类创建的流的指向既可以作为源也可以作为目的地。     构造方法:RandomAccessFile(String name,String mode) ; RandomAccessFile(File file,String mode) ;相关方法:seek(long a)  定位RandomAccessFile流的读写位置 getFilePointer() 获取流的当前读写位置   public native long getFilePointer() throws java.io.IOException;  public void seek(long) throws java.io.IOException;  public final boolean readBoolean() throws java.io.IOException;  public final byte readByte() throws java.io.IOException;  public final short readShort() throws java.io.IOException;  public final char readChar() throws java.io.IOException;  public final int readInt() throws java.io.IOException;  public final long readLong() throws java.io.IOException;  public final float readFloat() throws java.io.IOException;  public final double readDouble() throws java.io.IOException;  public final java.lang.String readLine() throws java.io.IOException;  public final void writeBoolean(boolean) throws java.io.IOException;  public final void writeByte(int) throws java.io.IOException;  public final void writeShort(int) throws java.io.IOException;  public final void writeChar(int) throws java.io.IOException;  public final void writeInt(int) throws java.io.IOException;  public final void writeLong(long) throws java.io.IOException;  public final void writeFloat(float) throws java.io.IOException;  public final void writeDouble(double) throws java.io.IOException;
  • [技术干货] javaGUI之对话框
                               第09课 GUI-对话框================================================简介:        JDialog类创建的对话框必须要依赖于某个窗口。       对话框分为无模式和有模式两种。            有模式的对话框:当这个对话框处于激活状态时,只让程序响应对话框内部的事件,                                           而且将堵塞其它线程的执行,用户不能再激活对话框所在程序中的其它窗口,直到该对话框消失不可见。           无模式对话框:当这个对话框处于激活状态时,能再激活其它窗口,也不堵塞其它线程的执行。 ------------------------------------------------------------------------------消息对话框----------------------------------------------javax.swing包中的JOptionPane类的静态方法:    public static void showMessageDialog(                       Component parentComponent,                       String message,                       String title,                       int messageType)------------------------------------------------------------输入对话框-------------------------------------------可以用javax.swing包中的JOptionPane类的静态方法:  public static  String showInputDialog(Component parentComponent,                                      Object message,                                      String title,                                      int messageType)-----------------------------------------------------------------------------------------确认对话框---------------------------------------------------------------------可以用javax.swing包中的JOptionPane类的静态方法:public static int showConfirmDialog(Component parentComponent,                                    Object message,                                    String title,                                    int optionType)------------------------------------------------------------颜色对话框-----------------------------------------------------------------------------可以用javax.swing包中的JColorChooser类的静态方法:                        public static Color showDialog(Component component,                                                String title,                                        Color initialColor)----------------------------------------------------------------------------------------------------自定义对话框--------------------------------------------------------------------------创建对话框与创建窗口类似,通过建立JDialog的子类来建立一个对话框类,然后这个类的一个实例,即这个子类创建的一个对象,就是一个对话框。对话框是一个容器,它的默认布局是BorderLayout,对话框可以添加组件,实现与用户的交互操作。 ---------------------------------------------------------------------------构造对话框的2个常用构造方法JDialog() 构造一个无标题的初始不可见的对话框,对话框依赖一个默认的不可见的窗口,该窗口由Java运行环境提供。JDialog(JFrame owner) 构造一个无标题的初始不可见的无模式的对话框,owner是对话框所依赖的窗口,如果owner取null,对话框依赖一个默认的不可见的窗口,该窗口由Java运行环境提供。
  • [技术干货] gui 事件处理机制
    主要内容:        ◆  Java事件处理机制        ◆ 常见事件及处理接口        ◆ Java绘图机制▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Java事件处理机制===============================================简介:       Java事件处理机制是一种基于授权的事件处理机制。-----------------------------------------------------------------------------------------------------------------------事件处理中的主要概念:     事件源,能够产生事件的对象都可以成为事件源 .     监视器,负责监视事件源的对象,当事件源产生事件时,监视器将调用方法对事件进行处理。                    事件源通过调用相应的方法将某个对象注册为自己的监视器。                     对于文本框,这个方法是:addActionListener(监视器);                     java语言中监视器都是使用接口来实现的。                    事件源注册监视器之后,相应的操作就会导致相应的事件的发生,并通知监视器,监视器就会作出相应的处理。      事件处理接口,接口中定义了处理事件的方法。                                监视器负责处理事件源发生的事件。                                监视器是一个对象,为了处理事件源发生的事件,监视器这个对象会自动调用接口中一个方法来处理事件。                                 Java规定:为了让监视器这个对象能对事件源发生的事件进行处理,                               创建该监视器对象的类必须声明实现相应的接口,那么当事件源发生事件时,监视器就自动调用被类重写的某个接口方法。----------------------------------------------------------------------------------------------------------------------------事件处理步骤:1.写一个监视器类     class Pliceman implements XXXListener     {          接口方法(XXXEvent e)          {               //.......           }      }2.创建事件源、监视器对象3.事件源注册监视器     事件源.addXXXListener(监视器);---------------------------------------------常见事件及处理接口=========================================================ActionEvent事件    ***------------------------------------------------------------------------------------- 1 ActionEvent事件源 :    文本框、按纽、菜单项、密码框和单选按纽都可以触发ActionEvent事件,即都可以成为ActionEvent事件的事件源。  2.注册监视器: ActionListener接口作为监视器    事件源.addActionListener(ActionListener listen)    将实现ActionListener接口的类的实例注册为事件源的监视器。  3.ActionListener接口:重写接口中的方法作为事件处理。    ActionListener接口在java.awt.event包中,该接口中只有一个方法:                     public void actionPerformed(ActinEvent e) ;        事件源触发ActionEvent事件后,监视器将发现触发的ActionEvent事件,然后调用接口中的这个方法对发生的事件作出处理。        ActionEvent类事先创建的事件对象就会传递给该方法的参数e。   4.ActionEvent类中的方法:下面的方法能够获取发生动作的事件源。            public Object getSource() --------- 调用该方法可以获取发生ActionEvent事件的事件源对象的引用。            public String getActionCommand()  ---------  调用该方法可以获取发生ActionEvent事件时,和该事件相关的一个命令字符串。------------------------------------------------------------------------------------------------------------------------------带事件处理的GUI程序总结:通常有3各类:监视器类public class Pliceman implements ActionListener {   //取数组的组件   //显示数据的组件   //设置取数据组件的方法   //设置显示数据组件的方法   //接口中的方法   public void actionPerformed(ActionEvent e) {        //取得数据        //处理数据        //显示数据   }}窗口类pulic MainFrame extends JFrame {   //组件   //监视器   public MainFrame() {       init();       initEvent();       //设置窗口的属性   }   void init() {       //设置布局       //创建组件       //添加组件   }   void initEvent() {       //创建监视器       //设置监视器的属性       //事件源注册监视器   }}主类--------------------------------------------------------ItemEvent事件-----------------------------------------------------------------------------------------------------------1.ItemEvent事件源 :           选择框、下拉列表都可以触发ItemEvent事件。2.注册监视器:ItemListener接口作为监视器             事件源.addItemListener(ItemListener listen)             将实现ItemListener接口的类的实例注册为事件源的监视器。 3.ItemListener接口:重写方法作为事件处理             ItemListener接口在java.awt.event包中,该接口中只有一个方法                       public void itemStateChanged(ItemEvent e)                  事件源触发ItemEvent事件后,监视器将发现触发的ItemEvent事件,                 然后调用接口中的itemStateChanged(ItemEvent  e)方法对发生的事件作出处理。                  ItemEvent类事先创建的事件对象就会传递给该方法的参数e。 4.ItemEvent类中的方法 :              getSource()方法返回发生Itemevent事件的事件源外              getItemSelectable()方法返回发生Itemevent事件的事件源。 ---------------------------------------------------------------------------------------------------DocumentEvent事件---------------------------------------------------------------------------------------------------------------------1.  DocumentEvent事件源 :                文本区所维护的文档能触发DocumentEvent事件  2. 注册监视器:DocumentListener作为监视器                事件源.addDucumentListener(DocumentListener listen)                      将实现DocumentListener接口的类的实例注册为事件源的监视器。 3. DocumentListener接口:重写接口中的方法作为事件处理    DocumentListener接口在javax.swing.event包中,该接口中有三个方法:          public void changedUpdate(DocumentEvent e)          public void removeUpdate(DocumentEvent e)          public void insertUpdate(DocumentEvent e)----------------------------------------------------------------------------------------------------------------------MouseEvent事件_1-----------------------------------------------------------------------------------------------------------------------1. 事件源:        任何组件上都可以发生鼠标事件,如:鼠标进入组件、退出组件、在组件上方单击鼠标、拖动鼠标等都触发鼠标事件,                 即导致MouseEvent类自动创建一个事件对象。 2.使用MouseListener接口可以处理以下5种操作触发的鼠标事件            事件源.addMouseListener(MouseListener listener)。                   在事件源上按下鼠标键、在事件源上释放鼠标键、在事件源上击鼠标键、鼠标进入事件源、鼠标退出事件源。           注册监视器:MouseListener接口作为监视器3. MouseListener接口中有如下方法:重写方法作为事件源的处理    mousePressed(MouseEvent) 负责处理在组件上按下鼠标键触发的鼠标事件   mouseReleased(MouseEvent) 负责处理在组件上释放鼠标键触发的鼠标事件   mouseEntered(MouseEvent) 负责处理鼠标进入组件触发的鼠标事件   mouseExited(MouseEvent) 负责处理鼠标离开组件触发的鼠标事件   mouseClicked(MouseEvent) 负责处理在组件上单击鼠标键触发的鼠标事件4. MouseEvent 中有下列几个重要的方法:         getX() 获取鼠标指针在事件源坐标系中的x-坐标。         getY() 获取鼠标指针在事件源坐标系中的y-坐标。        getModifiers() 获取鼠标的左键或右键。         getClickCount() 获取鼠标被单击的次数。          getSource() 获取发生鼠标事件的事件源。5. MouseMotionListener接口                 可以处理以下两种操作触发的鼠标事件,  在事件源上拖动鼠标、在事件源上移动鼠标。          事件源注册监视器的方法是:                    事件源.addMouseMotionListener(MouseMotionListener listener)。     MouseMotionListener接口中有如下方法:                mouseDragged(MouseEvent)  负责处理拖动鼠标触发的鼠标事件。                mouseMoved(MouseEvent)  负责处理移动鼠标触发的鼠标事件。------------------------------------------------------------------------------------------------------------------------------焦点事件-----------------------------------------------------------------------------------------------1.焦点事件源 :组件可以触发焦点事件。  2.注册监视器:组件可以使用    addFocusListener(FocusListener listener)     注册焦点事件监视器。  3.FocusListener接口 :        创建监视器的类必须要实现FocusListener接口,该接口有两个方法:      public void focusGained(FocusEvent e)      public void focusLost(FocusEvent e)        当发生FocusEvent事件时,监视器调用类实现的接口中的相应方法。 4.组件也可调用        public boolean requestFocusInWindow()         方法可以获得输入焦点。 --------------------------------------------------------------------------------------------------------------键盘事件--------------------------------------------------------------------------------------------------1. 事件源:            当一个组件处于激活状态时,敲击键盘上一个键就导致这个组件触发键盘事件。2.某个组件使用addKeyListener方法注册监视器。3.接口KeyListener中有如下方法:     public void keyPressed(KeyEvent e),     public void keyTyped(KeyEvent e),     public void KeyReleased(KeyEvent e)  4.(KeyEvent 相关方法:   public int getKeyCode()判断哪个键被按下、敲击或释放,返回一个键码值 。         getKeyChar()判断哪个键被按下、敲击或释放,返回键上的字符。-------------------------------------------------------------------------------------------------------窗口事件-------------------------------------------------------------------------------------WindowListener接口.   (1)  public void windowActivated(WindowEvent e)  当窗口从非激活状态到激活时,窗口的监视器调用该方法。(2) public void windowDeactivated(WindowEvent e)  当窗口激活状态到非激活状态时,窗口的监视器调用该方法。(3) public void windowClosing(WindowEvent e)  当窗口正在被关闭时,窗口的监视器调用该方法。(4) public void windowClosed(WindowEvent e)  当窗口关闭后,窗口的监视器调用该方法。(5) public void windowIconified(WindowEvent e)  当窗口图标化时,窗口的监视器调用该方法。(6) public void windowDeiconified(WindowEvent e)  当窗口撤消图标化时,窗口的监视器调用该方法。(7) public void windowOpened(WindowEvent e)  当窗口打开时,窗口的监视器调用该方法。--------------------------------------------------------------------------------------------------------适配器------------------------------------------------------------------------------------------------------------------------每个具有不止一个方法的AWT监听器接口都有一个实现了它的所有方法,但却不做任何工作的适配器类。适配器可以代替接口来处理事件,当Java提供处理事件的接口中多于一个方法时,Java相应地就提供一个适配器类,比如WindowAdapter类。适配器已经实现了相应的接口,例如WindowAdapter类实现了WindowListener接口。因此,可以使用WindowAdapte的子类创建的对象做监视器,在子类中重写所需要的接口方法即可。 WindowAdapter适配器 .ComponentAdapterContainerAdapterFocusAdapterKeyAdapterMouseAdapterMouseMotio**pterWindowAdapter===========================================匿名类实例或窗口做监视器匿名类的方便之处是匿名类的外嵌类的成员变量在匿名类中仍然有效,当发生事件时,监视器就比较容易操作事件源所在的外嵌类中的成员.当事件的处理比较简单、系统也不复杂时,使用匿名类做监视器是一个不错的选择。让事件源所在的类的实例作为监视器,能让事件的处理比较方便,这是因为,监视器可以方便的操作事件源所在的类中的其他成员。当事件的处理比较简单,系统也不复杂时,让事件源所在的类的实例作为监视器是一个不错的选择。===================================事件总结-------------------------------------------------------------------------------------------------------1.授权模式   Java的事件处理是基于授权模式,即事件源调用调用方法将某个对象注册为自己的监视器。2.接口回调    addXXXListener(XXXListener listener)方法中的参数是一个接口,listener可以引用任何实现了该接口的类所创建的对象,   当事件源发生事件时,接口listener立刻回调被类实现的接口中的某个方法。3.方法绑定    当事件源触发事件发生后,监视器准确知道去调用哪个方法。4.保持松耦合    当事件源触发事件发生后,系统知道某个方法会被执行,但无须关心到底是哪个对象去调用了这个方法,     因为任何实现接口的类的实例(做为监视器)都可以调用这个方法来处理事件。    ====================================================================================================Java绘图机制---------------------------------------------------------------Component类有一个方法public void paint(Graphics g),程序可以在其子类中重写这个方法。当程序运行时,java运行环境会用Graphicd2D(Graphics的一个子类)将参数g实例化,对象g就可以在重写paint方法的组件上内绘制图形、图像等 .Graphics2D的“画笔”分别使用draw和fill方法来绘制和填充一个图形。组件都是矩形形状,组件本身有一个默认的坐标系,组件的左上角的坐标值是(0,0)。java.awt.geom直线           Line2D.Double(double x1, double y1, double x2, double y2)矩形Rectangle2D.Double(double x, double y, double w, double h)圆角矩形RoundRectangle2D.Double(double x, double y, double w, double h);椭圆Ellipse2D.Double(double x, double y double w, double h);圆弧Arc2D.Double(double x, double y, double w, double h, double start, double extent, int type)        x, y, w, h-----------椭圆位置大小        start, -------------圆弧其实位置,单位:度        extent-----------划过的度的弧。单位:度。正值:逆时针;负值:顺时针。        type ---------------Arc2D.OPEN          Arc2D.CHORD                   Arc2D.PIE文本         g.drawString(s, x, y)二次曲线三次曲线多边形-----------------------------------------------------有时需要平移、缩放或旋转一个图形。可以使用AffineTransform类来实现对图形的这些操作。例如: AffineTransform trans=new AffineTransform(); 将图形沿顺时针或逆时针以(x,y)为轴点旋转number个弧度 :trans.rotate(60.0*3.1415927/180,100,100); translate(double a, double b) -----------------------移动              scale(double a, double b)  ------------放缩rotate(double number, double x, double y)-------------------------旋转                 x,y --------旋转圆心                 number--------旋转弧度g_2d.setTransform(trans);-------------------------------------------------两个图形进行布尔运算运算之前,必须分别用这两个图形创建两个Area区域对象,例如:    Area a1 = new Area(T1);    Area a2 = new Area(T2);a1就是图形T1所围成的区域;a2就是T2所围成的区域。那么,a1调用add方法:   a1.add(a2); -------------  或之后,a1就变成a1和a2经过布尔“或”运算后的图形区域。可以用Graphics2D对象g来绘制或填充一个Area对象(区域):  g.draw(a1);  g.fill(a1);   a1.intersect(a2); ---------与   a1.exclusiveOr(a2) ----------------异或   a1.subtract(a2) ------------差。---------------------------------------------------1.加载图像组件调用getToolkit()方法可以返回这个对象的引用。Tollkit类的对象调用方法   Image getImage(String fileNme)或      Image getImage(File file)。可以返回一个Image对象,该对象封装着参数file(或参数fileName)指定的图像文件。2.绘制图像 public boolean drawImage(Image img,int x,int y,ImageObserver observer);    参数img是被绘制的Image对象,x、y是要绘制指定图像的矩形的左上角所处的位置,observer是加载图像时的图像观察器 。-----------------------------------------------------------
  • [技术干货] java之字符串 string 必看
    StringString类代表字符串,这个是最基本的对字符串的类,这个也是使用比较多的类,这里就不再详细介绍了构造new String(String str)new String(StringBuilder str)new String(StringBuffer str)new String(byte[] bys,String charsetName) 通过使用指定的字符集解码指定的 byte 子数组,构造一个新的 String。常用方法str charAt(int index) 返回指定索引处的字符String concat(String str) 将指定字符串str连接到此字符串的结尾,返回连接成功后的字符,因此需要接受才能有效果boolean contains(CharSequence s) 判断此字符串是否包含指定的char值序列,这里的 CharSequence是一个接口,可以直接使用它的子类作为参数(String,StringBuffer,StringBuild)static String copyValueOf(char[] c) 将字符数组变成字符串并且返回static String copyValueOf(char[] c,int off,int count) 将截取的字符数组变成字符串并且返回,off是开始截取的下标,count是截取的个数boolean endWith(String s) 判断字符串是否是以s结尾boolean equals(Object o) 用于比较int indexOf(char c) 返回字符c在字符串中第一次出现的索引int indexOf(char c,int fromIndex) 从指定索引处开始搜索,查找第一次出现的索引int indexOf(String str) 返回指定子字符串在此字符串中第一次出现处的索引。int indexOf(String str,int fromIndex) 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。boolean isEmpty()int length()boolean matches(String regex) 是否匹配正则表达式trim() 返回字符串的副本,忽略前导空白和尾部空白。String substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串。String substring(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串的一个子字符串。
  • [技术干货] java之网络编程
    Java的Socket API提供了一个很方便的对象接口进行网络编程。本文用一个简单的TCP Echo Server做例子,演示了如何使用Java完成一个网络服务器。   用作例子的TCP Echo Server是按以下方式工作的:  当一个客户端通过TCP连接到服务器后,客户端可以通过这个连接发送数据到服务端,而服务端接收到数据后会把这些数据用同一个TCP连接发送回客户端。服务端会一直保持这个连接直到客户端关闭它为止。  因为服务器需要能同时处理多个客户端,我们先选用一个常见的多线程服务模型:  让一个Thread负责监听服务端口,当有新的连接建立的时候,这个监听的Thread会为这个连接创建一个新的Thread来处理它。这样,服务器可以接受多个连接,并让多个Thread来分别处理它们。  以下是相应的服务端程序:  public class EchoServer implements Runnable {  public void run() {  try {  ServerSocket svr = new ServerSocket(7);  while (true) {  Socket sock = svr.accept();  new Thread(new EchoSession(sock)).start();  }  } catch (IOException ex) {  throw new Exceptio**pter(ex);  }  }  }  这段代码先创建了一个ServerSocket的对象并让其监听在TCP端口7上,然后在一个循环中用accept()方法接收新的连接,并创建处理这一连接的Thread。实际处理每个客户端连接的逻辑包含在EchoSession这个类里面。  在以上代码中使用了Exceptio**pter这个类,它的作用是把一个checked Exception包装成RuntimeException。详细的说明可以参考避免在Java中使用Checked Exception 一文。  以下是EchoSession的代码:  public class EchoSession implements Runnable {  public EchoSession(Socket s) {  _sock = s;  }  public void run() {  try {  try {  InputStream input = _sock.getInputStream();  OutputStream output = _sock.getOutputStream();  byte [] buf = new byte [128];              while (true) {  int count = input.read(buf);  if (count == -1)  break;  output.write(buf, 0 , count);  }  } finally {  _sock.close();  }  } catch (IOException ex) {  throw new Exceptio**pter(ex);     }  }  protected Socket _sock = null;  }  EchoSession接受一个Socket对象作为构造参数,在其run()方法中,它不停的从这个Socket对象的InputStream里面读数据并写回到该Socket的OutputStream中去,直到这个连接被客户端关闭为止(InputStream的read方法返回-1)。  EchoSession需要一个线程来执行,这容易让人联想到用Thread来作为EchoSession的父类。不过,这样做不够灵活,开销也比较大。而选择让EchoSession实现Runnable接口就灵活得多。在接下来的使用Thread Pool的Echo Server中可以看到这一点。以上已经是一个完整的TCP Echo Server,不过随着客户不停的连接和断开,这个服务器会不停的产生和消除线程,而这两个都是比较‘昂贵'的操作。为了避免这种消耗,可以考虑采用Thread Pool的机制。  使用在一个简单的Thread缓冲池的实现一文中Thread Pool的实现,可以对EchoServer作如下修改(EchoSession无需做修改):  public class EchoServer implements Runnable {  public void run() {  try {  ServerSocket svr = new ServerSocket(7);   // 初始化Thread Pool  SyncQueue queue = new SyncQueue(10);  for (int i = 0; i < 10; i ++) {  new Thread(new Worker(queue)).start();  }  while (true) {  Socket sock = svr.accept();  // 把任务放入Thread Pool  queue.put(new EchoSession(sock));  }  } catch (IOException ex) {  throw new Exceptio**pter(ex);  }  }  }  这里可以看出让EchoSession实现Runnable接口的灵活性,无需修改它就可以在Thread Pool里使用。  在这个例子里使用的Thread Pool比较简单,没有动态调整Thread数量的功能,所以这个Echo Server最多只能同时服务10个客户端。然而通过重载SyncQueue,我们可以很方便地加入这个功能以突破这个限制。在对网络服务器的性能以及并发度要求很高的时候,让每个客户端由一个专门的Thread来处理有可能不能满足我们的要求(想象一下同时有数千个客户端的情况)。这时可以考虑使用Java的NIO API来构建服务器架构,因为NIO中IO操作都是非阻塞的,我们只需要很少的Thread就可以充分地利用CPU来处理多个客户端的请求。
  • [技术干货] java多线程并发下的单例模式
    单例模式应该是设计模式中比较简单的一个,也是非常常见的,但是在多线程并发的环境下使用却是不那么简单了,今天给大家分享一个我在开发过程中遇到的单例模式的应用。首先我们先来看一下单例模式的定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供。单例模式的要素:1.私有的静态的实例对象2.私有的构造函数(保证在该类外部,无法通过new的方式来创建对象实例)3.公有的、静态的、访问该实例对象的方法单例模式分为懒汉形和饿汉式懒汉式:应用刚启动的时候,并不创建实例,当外部调用该类的实例或者该类实例方法的时候,才创建该类的实例。(时间换空间)优点:实例在被使用的时候才被创建,可以节省系统资源,体现了延迟加载的思想。缺点:由于系统刚启动时且未被外部调用时,实例没有创建;如果一时间有多个线程同时调用LazySingleton.getLazyInstance()方法很有可能会产生多个实例。例子:123456789101112publicclassSingletonClass{ //私有构造函数,保证类不能通过new创建privateSingletonClass(){}privatestaticSingletonClassinstance=null; publicstaticSingletonClassgetInstance(){if(instance==null){ //创建本类对象 instance=newSingletonClass();}returninstance;} }饿汉式:应用刚启动的时候,不管外部有没有调用该类的实例方法,该类的实例就已经创建好了。(空间换时间。)优点:写法简单,在多线程下也能保证单例实例的唯一性,不用同步,运行效率高。缺点:在外部没有使用到该类的时候,该类的实例就创建了,若该类实例的创建比较消耗系统资源,并且外部一直没有调用该实例,那么这部分的系统资源的消耗是没有意义的。例子:123456789101112131415161718192021222324252627282930313233publicclassSingleton{//首先自己在内部定义自己的一个实例,只供内部调用privatestaticfinalSingletoninstance=newSingleton();//私有构造函数 privateSingleton(){}//提供了静态方法,外部可以直接调用publicstaticSingletongetInstance(){returninstance;}}下面模拟单例模式在多线程下会出现的问题/***懒汉式单例类*/publicclassLazySingleton{//为了易于模拟多线程下,懒汉式出现的问题,我们在创建实例的构造函数里面使当前线程暂停了50毫秒privateLazySingleton(){try{Thread.sleep(50);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("生成LazySingleton实例一次!");}privatestaticLazySingletonlazyInstance=null;publicstaticLazySingletongetLazyInstance(){if(lazyInstance==null){lazyInstance=newLazySingleton();}returnlazyInstance;}}测试代码:我们在测试代码里面新建了10个线程,让这10个线程同时调用LazySingleton.getLazyInstance()方法12345678910111213publicclassSingletonTest{publicstaticvoidmain(String[]args){ //创建十个线程调for(inti=0;i<10;i++){newThread(){@Overridepublicvoidrun(){LazySingleton.getLazyInstance();}}.start();}}}结果:生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!可以看出单例模式懒汉式在多线程的并发下也会出现问题,分析一下:多个线程同时访问上面的懒汉式单例,现在有两个线程A和B同时访问LazySingleton.getLazyInstance()方法。假设A先得到CPU的时间切片,A执行到if(lazyInstance==null)时,由于lazyInstance之前并没有实例化,所以lazyInstance==null为true,在还没有执行实例创建的时候此时CPU将执行时间分给了线程B,线程B执行到if(lazyInstance==null)时,由于lazyInstance之前并没有实例化,所以lazyInstance==null为true,线程B继续往下执行实例的创建过程,线程B创建完实例之后,返回。此时CPU将时间切片分给线程A,线程A接着开始执行实例的创建,实例创建完之后便返回。由此看线程A和线程B分别创建了一个实例(存在2个实例了),这就导致了单例的失效。解决办法:我们可以在getLazyInstance方法上加上synchronized使其同步,但是这样一来,会降低整个访问的速度,而且每次都要判断。那么有没有更好的方式来实现呢?我们可以考虑使用"双重检查加锁"的方式来实现,就可以既实现线程安全,又能够使性能不受到很大的影响。我们看看具体解决代码123456789101112131415161718192021222324publicclassLazySingleton{privateLazySingleton(){try{Thread.sleep(50);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("生成LazySingleton实例一次!");}privatestaticLazySingletonlazyInstance=null;publicstaticLazySingletongetLazyInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块if(lazyInstance==null){ //同步块,线程安全地创建实例 synchronized(LazySingleton.class){ //再次检查实例是否存在,如果不存在才真正地创建实例 if(lazyInstance==null){  lazyInstance=newLazySingleton();  } }}returnlazyInstance;}}这样我们就可以在多线程并发下安全应用单例模式中的懒汉模式。这种方法在代码上可能就不怎么美观,我们可以优雅的使用一个内部类来维护单例类的实例,下面看看代码12345678910111213publicclassGracefulSingleton{privateGracefulSingleton(){System.out.println("创建GracefulSingleton实例一次!");}//类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载privatestaticclassSingletonHoder{    //静态初始化器,由JVM来保证线程安全privatestaticGracefulSingletoninstance=newGracefulSingleton();}publicstaticGracefulSingletongetInstance(){returnSingletonHoder.instance;}}说一下我在实际开发中的场景:为了程序的高效率使用多线程并发,然而是循环调用,可能导致创建线程数过多,考虑采用线程池管理,这时候创建线程池仍然是处于循环调用中,也可能导致多个线程池,这时候就考虑使用单例模式。源代码:1234567891011121314151617181920publicclassThreadPoolFactoryUtil{privateExecutorServiceexecutorService; //在构造函数中创建线程池privateThreadPoolFactoryUtil(){//获取系统处理器个数,作为线程池数量intnThreads=Runtime.getRuntime().availableProcessors();executorService=Executors.newFixedThreadPool(nThreads);}//定义一个静态内部类,内部定义静态成员创建外部类实例privatestaticclassSingletonContainer{privatestaticThreadPoolFactoryUtilutil=newThreadPoolFactoryUtil();}//获取本类对象publicstaticThreadPoolFactoryUtilgetUtil(){returnSingletonContainer.util;}publicExecutorServicegetExecutorService(){returnexecutorService;}}涉及到一个静态内部类,我们看看静态内部类的特点:1、静态内部类无需依赖于外部类,它可以独立于外部对象而存在。2、静态内部类,多个外部类的对象可以共享同一个内部类的对象。3、使用静态内部类的好处是加强了代码的封装性以及提高了代码的可读性。4、普通内部类不能声明static的方法和变量,注意这里说的是变量,常量(也就是finalstatic修饰的属性)还是可以的,而静态内部类形似外部类,没有任何限制。可以直接被用外部类名+内部类名获得。
  • [技术干货] Java进阶学习:jar打包详解
    jar文件听说过吗,没有?或者陌生!好,没关系,这就是我们的第一站:打包发布。     为什么会有这个玩意呢,首先,这是jar的全称:JavaTM Archive (JAR) file,是的,就是java存档文件。这有点类似zip文件,想一想它是干什么的用的呢,压缩!?没错就是要压缩,将我们原先零散的东西放到一下,重新组织,所有这些目的只有一个:方便!好了,不用管他是怎么压缩的,我们的重点是哪些是我们要压缩的(输入),还有压缩成了什么(输出),进而将它发布(部署)。     那我们的输入(要压缩的东西)主要是class文件,还有辅助的资源(这其中可能有图片,jsp文件,html文件等等)。Jar技术在jdk1.1版本中就已存在,在1.2中又有了增强。接下来说说jar的好处吧,这是官方的描述:安全,快速下载,压缩,猎取包,版本化包,可携。    说了这么多,我们现在开始实施。     先打开命令提示符(win2000或在运行框里执行cmd命令,win98为DOS提示符),输入jar Chelp,然后回车(如果你盘上已经有了jdk1.1或以上版本),看到什么:     用法:jar {ctxu}[vfm0Mi] [jar-文件] [manifest-文件] [-C 目录] 文件名 ...     选项:     -c 创建新的存档  -t 列出存档内容的列表  -x 展开存档中的命名的(或所有的〕文件  -u 更新已存在的存档  -v 生成详细输出到标准输出上  -f 指定存档文件名  -m 包含来自标明文件的标明信息  -0 只存储方式;未用zip压缩格式  -M 不产生所有项的清单(manifest〕文件  -i 为指定的jar文件产生索引信息  -C 改变到指定的目录,并且包含下列文件:    如果一个文件名是一个目录,它将被递归处理。     清单(manifest〕文件名和存档文件名都需要被指定,按'm' 和 'f'标志指定的相同顺序。     示例1:将两个class文件存档到一个名为 'classes.jar' 的存档文件中:  jar cvf classes.jar Foo.class Bar.class    示例2:用一个存在的清单(manifest)文件 'mymanifest' 将 foo/ 目录下的所有文件存档到一个名为 'classes.jar' 的存档文件中:  jar cvfm classes.jar mymanifest -C foo/ .    来个小例子试试看:  我们只有一个HelloWorld,如下:     public class HelloWorld{   public static void main(String[ ] args){   System.out.println(“Hi, Hello World!”);   }   }     将这个java文件存到C盘跟目录下,ok,接下来,  在先前打开的命令提示符下(跳转到C盘提示符下),我们输入javac HelloWorld.java,然后继续输入:jar  cvf  hello.jar  HelloWorld.class,回车后去你的C盘看看,多了什么,没错 hello.jar 。  基本的步骤我们现在都知道了,你可以自己去尝试一下随着jar后面的参数的不同,结果有什么变化。      紧接着我们看看如何运行我们的jar包。   在进入正题之前,你要先打开我们刚刚做好的jar包看看,多了什么呢,META-INF目录?再看看里面是什么,还有一个MANIFEST.MF文件是不是?用文本编辑器(我这里是UltraEdit)打开它看看:  Manifest-Version: 1.0   Created-By: 1.4.2 (Sun Microsystems Inc.)    就是这样。这里我们对它进行修改,加一句:Main-Class: HelloWorld (在第三行)。这个就是我们之前写的那个类,也就是我们的入口类。也即,  Manifest-Version: 1.0   Created-By: 1.4.2 (Sun Microsystems Inc.)   Main-Class: HelloWorld    接下来,我们在命令提示符里执行:  jar  umf  MANIFEST.MF  app.jar  (应该是hello.jar吧)   这样我们使用了我们自己的MANIFEST.MF文件对原来默认的进行了更新。你不妨可以再进去看看是不是添上了Main-Class: HelloWorld这一句。    (是吗,我怎么没试出来,提示java.io.FileNotFoundException:MANIFEST.MF(系统找不到指定的文件)怎么回事?) Ok,这个最后的一步了,来验证我们做的一切,在命令提示符中输入:  java -jar hello.jar(执行)    出现了什么, Hi, Hello World!    我们再来看看jar文件在tomcat中发布,注意:在tomcat中我们就不能再用jar这种格式,而改war格式,它是专门用于web应用的,其实整个过程下来基本上和jar是类似的:   先准备我们要打包的资源。     找到存放tomcat的webapps目录,进到其中,新建一个文件夹,这里命名为hello,再进去新建WEB-INF文件夹,再进去新建classes文件夹,此时我们也将我们唯一的servlet,HelloWorld.java放到这里,在与classes目录同级下建立一文件web.xml。Ok,目前我们初步建立了一个简单的web应用。    这是HelloWorld.java:     import java.io.*;   import javax.servlet.*;   import javax.servlet.http.*;   public class HelloWorld extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)   throws ServletException, IOException {   res.setContentType("text/html");   PrintWriter out = res.getWriter();   out.println("");   out.println("");   out.println("");   out.println("Hello, World!");   out.println("");   }   }//end here!    对它编译。下面是web.xml:   <?xml version="1.0" encoding="UTF-8"?>   <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">   <web-app>   <servlet>   <servlet-name>hello</servlet-name>   <servlet-class>HelloWorld</servlet-class>   </servlet>   <servlet-mapping>   <servlet-name>hello</servlet-name>   <url-pattern>/HelloWorld</url-pattern>   </servlet-mapping>   </web-app>  在命令提示符下进到先前创制的hello目录下,执行 jar cvf hello.war * ,我们便得到hello.war。将它拷贝至webapps目录下,ok,来看最后一步,打开tomcat的目录conf中的server.xml,加入:  <Context path="/hello" docBase="hello.war" debug="0" reloadable="true"/>   大功告成!运行它,启动tomcat,后在浏览器中输入http://localhost:8080/hello/HelloWorld,有了吗?   最后,如果你想用ant来完成以上的打包活动,下面就告诉你:   对于jar来说。在build.xml中,  <target name="jar">   <jar destfile="${app_home}/hello.jar">   <fileset dir="${dest}" includes="**"/>   <!--fileset dir="${dest}" includes="**/action.properties"/-->   </jar>   </target>     对于war,  <war warfile="hello.war" webxml="./WEB-INF/web.xml">   <fileset dir="html"/>   <lib dir="lib/">   <exclude name="oracle*.jar"/>   </lib>   <classes dir="build/servlets">   <include name="**/*.class"/>   </classes>   </war>     好了,就这么多,希望对你有点帮助。:)  补充:   jar基本操作:     1. 创建jar文件    jar cf jar-file input-file(s)   c---want to Create a JAR file.   f---want the output to go to a file rather than to stdout.   eg: 1)jar cf myjar.jar query_maintain_insert.htm   2)jar cvf myjar.jar query_maintain_insert.htm   v---Produces verbose(详细的) output.  3)jar cvf myjar.jar query_maintain_insert.htm mydirectory   4)jar cv0f myjar.jar query_maintain_insert.htm mydirectory   0---don't want the JAR file to be compressed.   5)jar cmf MANIFEST.MF myjar.jar yahh.txt   m---Used to include manifest information from an existing manifest file.   6)jar cMf MANIFEST.MF myjar.jar yahh.txt   M---the default manifest file should not be produced.   7)jar cvf myjar.jar *   *---create all contents in current directory.   2. 察看jar文件     jar tf jar-file   t---want to view the Table of contents of the JAR file.   eg: 1)jar vft yahh.jar   v---Produces verbose(详细的) output.    3. 提取jar文件    jar xf jar-file [archived-file(s)]   x---want to extract files from the JAR archive.   eg: 1)jar xf yahh.jar yahh.txt(仅提取文件yahh.txt)  2)jar xf yahh.jar alex/yahhalex.txt(仅提取目录alex下的文件yahhalex.txt)    3)jar xf yahh.jar(提取该jar包中的所有文件或目录)    4. 修改Manifest文件    jar cmf manifest-addition jar-file input-file(s)   m---Used to include manifest information from an existing manifest file.    5. 更新jar文件    jar uf jar-file input-file(s)   u---want to update an existing JAR file.
  • [技术干货] 浅谈java中的对象、类、与方法的重载
    对象:一切皆为对象。对象包括两部分内容:属性(名词形容词),行为(动词)。对象和对象之间是有关系的: 派生,关联,依赖。类:对同一类别的众多对象的一种抽象。类,还是用来生成对象的一种模板,对象是类的一种具体化的表现。面向对象的三大特性:封装,继承,多态。1234class 类名{访问修饰符 成员变量的定义;访问修饰符 成员函数(方法)的定义; }访问修改符:默认不写,private,public。private,私有。只能被当前class 类名{}中的代码访问到。出了这个类的{},访问不到了。public,公有。能被当前class 类名{}内访问。也能被其它位置的对象访问。默认不写,在当前class 类名{}中可以访问。当前包中的其它类中也可以访问。出了当前包,就访问不到了get方法和set方法1.get成员变量名()方法:当成员变量是private的时候,用来获取成员变量。1.set成员变量名()方法:当成员变量是private的时候,用来直接改变成员变量。类的实例化:用类来生成对象。类名 变量名 = new 类名();类名 变量名 = new 构造函数;构造函数:必须与类名相同。不能有返回类型。自动被执行。对象new出的来时间自动被执行。一般都是public。先执行成员变量的初始化,在执行成员函数。不写一般也会被系统默认创建。1234567891011121314151617public class gou { //成员变量 private String mingZi = "大黄" ;  private String zhuRen; //成员函数(方法) public String getMingZi() {  return mingZi; } public void setMingZi(String mingZi) { if(mingZi != zhuRen){  this.mingZi = mingZi; }  } public void jiaoHuan(){ System.out.println(mingZi+":旺!旺!旺!"); }}123456public class home { public static void main(String[] args) { gou a = new gou(); a.setMingZi("小黄");     //用set方法修改名字。 System.out.println("小狗的名字是:"+a.getMingZi()); //用get方法获取名字。 a.jiaoHuan();最后显示小黄小狗的名字是:汪汪汪方法的重载:函数名相同,参数不同(数量,类型)。这两个函数就形成了重载。重载与函数返回类型号没有关系,重载只跟函数名和参数有关系。重载了的函数,会自动跟据调用的实参来决定调用哪个函数。除了构造函数可以重载,普通成员函数也可以重载
  • [技术干货] Java中Runnable和Thread的区别分析
    Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但是一个类只能继承一个父类,这是此方法的局限,  下面看例子:复制代码代码如下:  package org.thread.demo;  class MyThread extends Thread{  private String name;  public MyThread(String name) {  super();  this.name = name;  }  public void run(){  for(int i=0;i<10;i++){  System.out.println("线程开始:"+this.name+",i="+i);  }  }  }  package org.thread.demo;  public class ThreadDemo01 {  public static void main(String[] args) {  MyThread mt1=new MyThread("线程a");  MyThread mt2=new MyThread("线程b");  mt1.run();  mt2.run();  }  }  但是,此时结果很有规律,先第一个对象执行,然后第二个对象执行,并没有相互运行。在JDK的文档中可以发现,一旦调用start()方法,则会通过JVM找到run()方法。下面启动  start()方法启动线程:复制代码代码如下:  package org.thread.demo;  public class ThreadDemo01 {  public static void main(String[] args) {  MyThread mt1=new MyThread("线程a");  MyThread mt2=new MyThread("线程b");  mt1.start();  mt2.start();  }  };    这样程序可以正常完成交互式运行。那么为啥非要使用start();方法启动多线程呢?  在JDK的安装路径下,src.zip是全部的java源程序,通过此代码找到Thread中的start()方法的定义,可以发现此方法中使用了private native void start0();其中native关键字表示可以调用操作系统的底层函数,那么这样的技术成为JNI技术(java Native Interface)  ·Runnable接口  在实际开发中一个多线程的操作很少使用Thread类,而是通过Runnable接口完成。复制代码代码如下:  public interface Runnable{  public void run();  }  例子:复制代码代码如下:  package org.runnable.demo;  class MyThread implements Runnable{  private String name;  public MyThread(String name) {  this.name = name;  }  public void run(){  for(int i=0;i<100;i++){  System.out.println("线程开始:"+this.name+",i="+i);  }  }  };  但是在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。此时观察Thread类,有一个构造方法:public Thread(Runnable targer)此构造方法接受Runnable的子类实例,也就是说可以通过Thread类来启动Runnable实现的多线程。(start()可以协调系统的资源):复制代码代码如下:  package org.runnable.demo;  import org.runnable.demo.MyThread;  public class ThreadDemo01 {  public static void main(String[] args) {  MyThread mt1=new MyThread("线程a");  MyThread mt2=new MyThread("线程b");  new Thread(mt1).start();  new Thread(mt2).start();  }  }  · 两种实现方式的区别和联系:  在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比  继承Thread类有如下好处:  ->避免点继承的局限,一个类可以继承多个接口。  ->适合于资源的共享  以卖票程序为例,通过Thread类完成:复制代码代码如下:  package org.demo.dff;  class MyThread extends Thread{  private int ticket=10;  public void run(){  for(int i=0;i<20;i++){  if(this.ticket>0){  System.out.println("卖票:ticket"+this.ticket--);  }  }  }  };  下面通过三个线程对象,同时卖票:复制代码代码如下:  package org.demo.dff;  public class ThreadTicket {  public static void main(String[] args) {  MyThread mt1=new MyThread();  MyThread mt2=new MyThread();  MyThread mt3=new MyThread();  mt1.start();//每个线程都各卖了10张,共卖了30张票  mt2.start();//但实际只有10张票,每个线程都卖自己的票  mt3.start();//没有达到资源共享  }  }  如果用Runnable就可以实现资源共享,下面看例子:复制代码代码如下:  package org.demo.runnable;  class MyThread implements Runnable{  private int ticket=10;  public void run(){  for(int i=0;i<20;i++){  if(this.ticket>0){  System.out.println("卖票:ticket"+this.ticket--);  }  }  }  }  package org.demo.runnable;  public class RunnableTicket {  public static void main(String[] args) {  MyThread mt=new MyThread();  new Thread(mt).start();//同一个mt,但是在Thread中就不可以,如果用同一  new Thread(mt).start();//个实例化对象mt,就会出现异常  new Thread(mt).start();  }  };
  • [技术干货] java的排序算法
    一、选择排序(SelectSort)基本原理:对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将该记录与第一个记录的位置进行交换;接着对不包括第一个记录以外的其他记录进行第二次比较,得到最小的记录并与第二个记录进行位置交换;重复该过程,直到进行比较的记录只有一个为止。1234567891011121314151617181920212223242526272829public class SelectSort { public static void selectSort(int[] array) { int i; int j; int temp; int flag; for (i = 0; i < array.length; i++) { temp = array[i]; flag = i; for (j = i + 1; j < array.length; j++) { if (array[j] < temp) {  temp = array[j];  flag = j; } } if (flag != i) { array[flag] = array[i]; array[i] = temp; } } } public static void main(String[] args) { int[] a = { 5, 1, 9, 6, 7, 2, 8, 4, 3 }; selectSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}二、插入排序(InsertSort)基本原理:对于给定的一组数据,初始时假设第一个记录自成一个有序序列,其余记录为无序序列。接着从第二个记录开始,按照记录的大小依次将当前处理的记录插入到其之前的有序序列中,直至最后一个记录插入到有序序列中为止。12345678910111213141516171819202122232425public class InsertSort { public static void insertSort(int[] a) { if (a != null) { for (int i = 1; i < a.length; i++) { int temp = a[i]; int j = i; if (a[j - 1] > temp) {  while (j >= 1 && a[j - 1] > temp) {  a[j] = a[j - 1];  j--;  } } a[j] = temp; } } } public static void main(String[] args) { int[] a = { 5, 1, 7, 2, 8, 4, 3, 9, 6 }; // int[] a =null; insertSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}三、冒泡排序(BubbleSort)基本原理:对于给定的n个记录,从第一个记录开始依次对相邻的两个记录进行比较,当前面的记录大于后面的记录时,交换位置,进行一轮比较和换位后,n个记录中的最大记录将位于第n位;然后对前(n-1)个记录进行第二轮比较;重复该过程直到进行比较的记录只剩下一个为止。12345678910111213141516171819202122public class BubbleSort { public static void bubbleSort(int array[]) { int temp = 0; int n = array.length; for (int i = n - 1; i >= 0; i--) { for (int j = 0; j < i; j++) { if (array[j] > array[j + 1]) {  temp = array[j];  array[j] = array[j + 1];  array[j + 1] = temp; } } } } public static void main(String[] args) { int a[] = { 45, 1, 21, 17, 69, 99, 32 }; bubbleSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}四、归并排序(MergeSort)基本原理:利用递归与分治技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归方法将排好序的半子表合并成为越来越大的有序序列。对于给定的一组记录(假设共有n个记录),首先将每两个相邻的长度为1的子序列进行归并,得到n/2(向上取整)个长度为2或1的有序子序列,再将其两两归并,反复执行此过程,直到得到一个有序序列。12345678910111213141516171819202122232425262728293031323334353637383940414243444546public class MergeSort { public static void merge(int array[], int p, int q, int r) { int i, j, k, n1, n2; n1 = q - p + 1; n2 = r - q; int[] L = new int[n1]; int[] R = new int[n2]; for (i = 0, k = p; i < n1; i++, k++) L[i] = array[k]; for (i = 0, k = q + 1; i < n2; i++, k++) R[i] = array[k]; for (k = p, i = 0, j = 0; i < n1 && j < n2; k++) { if (L[i] > R[j]) { array[k] = L[i]; i++; } else { array[k] = R[j]; j++; } } if (i < n1) { for (j = i; j < n1; j++, k++) array[k] = L[j]; } if (j < n2) { for (i = j; i < n2; i++, k++) { array[k] = R[i]; } } } public static void mergeSort(int array[], int p, int r) { if (p < r) { int q = (p + r) / 2; mergeSort(array, p, q); mergeSort(array, q + 1, r); merge(array, p, q, r); } } public static void main(String[] args) { int a[] = { 5, 4, 9, 8, 7, 6, 0, 1, 3, 2 }; mergeSort(a, 0, a.length - 1); for (int j = 0; j < a.length; j++) { System.out.print(a[j] + " "); } }}五、快速排序(QuickSort)基本原理:对于一组给定的记录,通过一趟排序后,将原序列分为两部分,其中前一部分的所有记录均比后一部分的所有记录小,然后再依次对前后两部分的记录进行快速排序,递归该过程,直到序列中的所有记录均有序为止。12345678910111213141516171819202122232425262728293031323334public class QuickSort { public static void sort(int array[], int low, int high) { int i, j; int index; if (low >= high) return; i = low; j = high; index = array[i]; while (i < j) { while (i < j && index <= array[j]) j--; if (i < j) array[i++] = array[j]; while (i < j && index > array[i]) i++; if (i < j) array[j--] = array[i]; } array[i] = index; sort(array, low, i - 1); sort(array, i + 1, high); } public static void quickSort(int array[]) { sort(array, 0, array.length - 1); } public static void main(String[] args) { int a[] = { 5, 8, 4, 6, 7, 1, 3, 9, 2 }; quickSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}六、希尔排序(ShellSort)基本原理:先将待排序的数组元素分成多个子序列,使得每个子序列的元素个数相对减少,然后对各个子序列分别进行直接插入排序,待整个待排序序列"基本有序后",最后再对所有元素进行一次直接插入排序。123456789101112131415161718192021222324252627public class ShellSort { public static void shellSort(int[] a) { int len = a.length; int i, j; int h; int temp; for (h = len / 2; h > 0; h = h / 2) { for (i = h; i < len; i++) { temp = a[i]; for (j = i - h; j >= 0; j -= h) {  if (temp < a[j]) {  a[j + h] = a[j];  } else  break; } a[j + h] = temp; } } } public static void main(String[] args) { int a[] = { 5, 4, 9, 8, 7, 6, 0, 1, 3, 2 }; shellSort(a); for (int j = 0; j < a.length; j++) { System.out.print(a[j] + " "); } }}七、最小堆排序(MinHeapSort)基本原理:对于给定的n个记录,初始时把这些记录看作一颗顺序存储的二叉树,然后将其调整为一个小顶堆,然后将堆的最后一个元素与堆顶元素进行交换后,堆的最后一个元素即为最小记录;接着讲前(n-1)个元素重新调整为一个小顶堆,再将堆顶元素与当前堆的最后一个元素进行交换后得到次小的记录,重复该过程直到调整的堆中只剩一个元素时为止,该元素即为最大记录,此时可得到一个有序序列。123456789101112131415161718192021222324252627282930313233343536public class MinHeapSort { public static void adjustMinHeap(int[] a, int pos, int len) { int temp; int child; for (temp = a[pos]; 2 * pos + 1 <= len; pos = child) { child = 2 * pos + 1; if (child < len && a[child] > a[child + 1]) child++; if (a[child] < temp) a[pos] = a[child]; else break; } a[pos] = temp; } public static void myMinHeapSort(int[] array) { int i; int len = array.length; for (i = len / 2 - 1; i >= 0; i--) { adjustMinHeap(array, i, len - 1); } for (i = len - 1; i >= 0; i--) { int tmp = array[0]; array[0] = array[i]; array[i] = tmp; adjustMinHeap(array, 0, i - 1); } } public static void main(String[] args) { int[] a = { 5, 4, 9, 8, 7, 6, 0, 1, 3, 2 }; myMinHeapSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}
总条数:2301 到第 页
上滑加载中