Dev
S
O
N
G
主页
分类
时间轴
涂鸦
关于
留言
工具箱
Dev
S
O
N
G
Dev
S
O
N
G
一个理想主义者的博客
主页
分类
时间轴
涂鸦
关于
留言
工具箱
TimeClip——一个实用的剪贴板小工具
附带源码,欢迎交流
......
It's
欢迎访问本站,欢迎留言、分享、点赞。愿您阅读愉快!
https://www.devsong.org
实习的第三天
原定于7月10号的实习提前三天开始了,这是第三天。想了很多事情,也面临着很大,压力来自于即将到来的秋季招聘,这个时候本应准备着即将到来的招聘会,我却做着与此毫不相关的事情。不得不说,心里很矛盾,但是该进行的还是得进行。并且这实习是早上六点半出发,晚上十点左右才能回学校,所以学习的时间在这几天算是没有了。现在能做的,也只有好好计划一下这个所谓的实习结束后的学习路线了。值得庆幸的是这个所谓的实习时间不长。 如上所说,往好的方向想,静下心来好好规划接下来的路线也不是件坏事。加油!
2017-07-10
时光记
HTML标签删除、转义、反转义
有时候我们需要提取HTML的内容而不希望包含HTML标签,这时候利用正则表达式就是一个非常好的选择。下面给出一个不怎么严谨的写法: public static String delHtmlTag(String htmlStr) { String regEx_script = "<script[^>]* >[\\s\\S]* <\\/script>"; // 定义script的正则表达式 String regEx_style = "<style[^>]* >[\\s\\S]* <\\/style>"; // 定义style的正则表达式 String regEx_html = "<[^>]+>"; // 定义HTML标签的正则表达式 //附加配置:忽略大小写 Pattern p_script = Pattern.compile(regEx_script, Pattern.CASE_INSENSITIVE); Matcher m_script = p_script.matcher(htmlStr); htmlStr = m_script.replaceAll(""); // 过滤script标签 Pattern p_style = Pattern.compile(regEx_style, Pattern.CASE_INSENSITIVE); Matcher m_style = p_style.matcher(htmlStr); htmlStr = m_style.replaceAll(""); // 过滤style标签 Pattern p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE); Matcher m_html = p_html.matcher(htmlStr); htmlStr = m_html.replaceAll(""); // 过滤html标签 return htmlStr.trim(); // 返回文本字符串 } 你可能也看出来了,当HTML的一个内容中包含成对的大于小于符号时,也就构成了“<>”,那么其中的内容会被当成HTML标签属性内容而删掉。所以说不够严谨。 HTML标签的转义、反转义 java中可以借助第三方的工具方便的完成,apache.commons.lang包包含了很多常用的工具,这其中有一个非常好用的字符串转义工具:StringEscapeUtils.利用它可以方便的对字符进行转义与反转义,而且是细化到不同语言对象,常见的Java、Html、JavaScript等代码它都能搞定非常好用。 随便看一下其方法,很强大有木有: 简单写个demo测试一下: String str = "<h1 style='width:100px; height:100px'>hello</h1>"; System.out.println("测试字符串:" + str); String str1 = StringEscapeUtils.escapeHtml4(str); System.out.println("转义:" + str1); String str2 = StringEscapeUtils.unescapeHtml4(str1); System.out.println("反转义:" + str2); 输出: 测试字符串:<h1 style='width:100px; height:100px'>hello</h1> 转义:<h1 style='width:100px; height:100px'>hello</h1> 反转义:<h1 style='width:100px; height:100px'>hello</h1> 一切正常。 注意:从Commons版本3.6开始,StringEscapeUtils被转移到了org.apache.commons.text包下,所以继续用commons.lang下的StringEscapeUtils会显示已过时。建议使用commons.text包下的StringEscapeUtils。 前端的相关操作 有时候以上操作可能发生在前端,这里我也收集了相关的JavaScript方法: //删除HTML标签(与Java同样的正则表达式,所以依旧不太严谨) function delHtmlTag(str) { return str.replace(/<[^>]+>/g, ""); } //HTML标签转义 function html2Escape(sHtml) { return sHtml.replace(/[<>&"]/g, function (c) { return {'<': '<', '>': '>', '&': '&', '"': '"'}[c]; }); } //HTML标签反转义 function escape2Html(str) { var arrEntities = {'lt': '<', 'gt': '>', 'nbsp': ' ', 'amp': '&', 'quot': '"'}; return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, function (all, t) { return arrEntities[t]; }); }
2017-07-08
HTML/CSS
阿里云OSS的简单配置
对象存储服务(Object Storage Service,简称 OSS),就是一种云端的存储服务。简单来说就是类似百度云的东西,但又比百度云高端,对象存储服务更多的权限在自己,通过简单配置可以完成很多复杂耗时的任务,如果你愿意,你完全可以用它打造一个网盘应用。 总的来说就是你有一个云端存储空间,而空间支配权在于你,你可以将其作为APP的后端数据存储,如图片视频之类的,也可以分配给你的用户,用于上传个人文件......相信你看到本篇文章的时候对OSS一定有一些了解了。如果您已经非常熟悉相关操作,您可以跳过这篇笔记了,当然,如果您能在此情况下阅读完这篇笔记并提出更好的见解那自然是最好不过的。 【注:如果您是初次接触OSS,那么读完本篇文章你或许或有些困惑,毕竟本篇文章只是记录了服务器端和前端的配置,除此之外的一些内容还需要您到阿里云官网查看相关内容,包括bucket的建立、跨域的相关配置等,包括一些专有的名词你也能在官网找到解释。链接:点此跳转】 接下来进入正题了。本篇笔记写的是阿里云OSS的相关配置,如果你用的是其他平台的OSS,可能会有些差别。 先说说要实现的功能:按照阿里云的说法,直接在前端利用ACCESSID和ACCESSKEY上传文件是不安全的,的确也是这样,如果别人拿到了你的id和key,那么你的所有东西就都暴露了。所以要使用前端直传的话比较好的方式是从服务器获取签名,这样不会向前端暴露key。 工作流程: 服务端签名配置 服务端利用阿里云OSS的SDK开发包直接就能生成所需的签名及上传策略,这里直接给出参考代码: import com.aliyun.oss.OSSClient; import com.aliyun.oss.common.utils.BinaryUtil; import com.aliyun.oss.model.MatchMode; import com.aliyun.oss.model.PolicyConditions; import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; public class StsService { public Map getSts() { // 只有 RAM用户(子账号)才能调用 AssumeRole 接口 // 阿里云主账号的AccessKeys不能用于发起AssumeRole请求 // 请首先在RAM控制台创建一个RAM用户,并为这个用户创建AccessKeys String accessId = "XXXXXXXXXX"; //你的accessid String host = "XXXXXXXXX"; //存储地址: http:// + 你的bucket + / + 你的endpoint String dir=""; //建立客户端实例 OSSClient client = new OSSClient(你的endpoint, 你的accessid, 你的accessKey); //设置到期时间 3600 秒 long expireTime = 3600; //转化为毫秒 并 加上当前时间点 long expireEndTime = System.currentTimeMillis() + expireTime * 1000; Date expiration = new Date(expireEndTime); PolicyConditions policyConds = new PolicyConditions(); policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000); policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir); //生成上传策略 String postPolicy = client.generatePostPolicy(expiration, policyConds); byte[] binaryData = new byte[0]; try { binaryData = postPolicy.getBytes("utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //base64编码 String encodedPolicy = BinaryUtil.toBase64String(binaryData); //根据上传策略计算签名 String postSignature = client.calculatePostSignature(postPolicy); //将所需的信息放入map准备发给前端(自己根据需求, accessid、policy、signature是必须有的,其余的看需求) Map<String, String> respMap = new LinkedHashMap<String, String>(); respMap.put("accessId", accessId); respMap.put("policy", encodedPolicy); respMap.put("signature", postSignature); //以下非必须,看需求 respMap.put("host", host); respMap.put("expire", String.valueOf(expireEndTime / 1000)); return respMap; } } 我在上述代码中已经做了相关注释,根据需求更改map中的内容并将accessId、accessKey、endPoint等替换之后就能使用了。只需将respMap传给客户端就行了。 客户端配置 客户端的操作主要是先请求policy,然后提取得到上述服务器返回的内容:policy、accessId、signature等。与服务器交互的方式我就不写,实现方式多种多样。只要最后能得到所需内容即可。 得到这些东西后就能进行文件上传操作了。这里借助formData上传文件,这里看个人喜好,你也可以不用formData: function uploadFile() { //新建formData对象 var formData = new FormData(); //获取文件 var file = $(".file_input")[0].files[0]; //加入文件及相关签名信息 formData.append("OSSAccessKeyId", accessId); formData.append("signature", signature); formData.append("policy", policy); formData.append("key", “存储文件的位置”); //存储文件的位置及文件名,比如“test/test.jpg”表示将文件存储到test下并命名为test.jpg formData.append("file", file); //向dataForm添加文件,注意,文件必须放在最后一项添加 $.ajax({ url:你的host, //OSS地址:http:// + 你的bucket名称 + / + 你的OSS对应的endpoint processData:false, contentType:false, type:"POST", data:formData, success:function () { alert("success"); }, error:function (data) { alert("error"); } }) } 【注:上述说到的endpoint等都是在阿里云官网可以查到的,不同的区域对应的endpoint不一样,若您发现此文章提到一个新的东西但并未做介绍你可以在文章开头的提供的连接中找到】 至此,配置就算完成了。若您有疑问,可以参看阿里云官网提供的开发文档。当然,也欢迎给我留言,共同探讨学习。 【devsong.org原创内容,转载请注明出处】
2017-07-04
Java
按字节长度截取字符串
平时我们截取字符串一般都是按照可见字符个数直接用substring()方法截取,非常方便且实用,只要当前String对象本身不是乱码我们便能拿到想要的长度。这样的截取方法可以解决大部分应用场景下的问题。 但是也有部分场景是这样操作不能解决的,那就是按字节大小截取指定字符串。涉及到字节数截取,很容易想到一个问题:编码问题,大部分支持中文的编码都是以两个字节来表示一个中文字符,也有用3个字节表示中文的utf-8编码,针对这些不同编码的字符串截取就要考虑到有可能截取到残缺中文的问题。 其实也不是什么大问题,只要正确地得到了字节数组或者字符串(与当前软件运行环境相对)就好办了。因为知道了字符编码就能结合当前编码特点进行截取,避免出现截取残缺中文的情况。下面针对gbk(在简体中文Windows操作系统中,ANSI 编码就代表 GBK 编码)和utf-8这两种比较常见的编码给出个demo,其他编码也是类似的。 package org.devsong; import java.io.IOException; import java.io.UnsupportedEncodingException; public class EncodingTest { public static void main(String[] args) throws IOException { // 工作空间决定其编码,当前测试环境为utf-8,且保存源码文件为utf-8 String str = "你好啊hello"; // 为方便测试,这里只是简单获取byte数组传入,实际中byte可能从外部传入,比如网络等 // utf-8: 截取 8 个字节的长度,很明显容不下 3 个汉字,所以只截取了 2 个汉字: 你好 System.out.println(cutStrWithUTF8(str.getBytes("utf-8"), 8)); // gbk: 截取 8 个字节的长度: 你好啊he System.out.println(cutStrWithGBK(str.getBytes("gbk"), 8)); } public static String cutStrWithUTF8(byte[] utf8Bytes, int byteCnt) throws UnsupportedEncodingException { int zhCnt = 0; int cutNum = 0; if(byteCnt <= 0) { return ""; } if(byteCnt >= utf8Bytes.length) { return new String(utf8Bytes, "utf-8"); } // UTF-8中,汉字以3个字节表示,且3个字节的ASCII码都大于128, 即为 负 for (int i = 0; i < byteCnt; i++) { if (utf8Bytes[i] < 0) { // 判断是否为汉字 zhCnt++; } } //utf-8中一个汉字是由两个字节组成的 if (zhCnt % 3 == 0) { // 如果汉字的字节数能被 3 整除,则说明当前长度截取不会截到不完整的汉字 cutNum = byteCnt; } else if (zhCnt % 3 == 2) { // 若余量为 2, 则按当前长度截取会存在残缺的汉字,所以此处截取字节数减 2,当然也可以加 1,这里看实际需求和个人喜好 cutNum = byteCnt - 2; } else { // 若余量为 1,则也会截取到不完整的汉字,所以减一,亦可加 二,理由同上。总之只要汉字字符个数能被三整除就不会存在残缺汉字字符的情况 cutNum = byteCnt - 1; } return new String(utf8Bytes, 0, cutNum, "utf-8"); } public static String cutStrWithGBK(byte[] gbkBytes, int byteCnt) throws UnsupportedEncodingException { if(byteCnt <= 0) { return ""; } if(byteCnt >= gbkBytes.length) { return new String(gbkBytes, "gbk"); } int zhCnt = 0; for (int i = 0; i < byteCnt; i++) { // gbk中,汉字以 2 个字节表示,且 2 个字节的ASCII码都大于128, 即为 负 if (gbkBytes[i] < 0) { // 判断是否为汉字 zhCnt++; } } int cutNum = byteCnt; if (zhCnt % 2 != 0) { // gbk中一个汉字是由两个字节组成的 // 若余量不等于0,也就是余量为1,则也会截取到不完整的汉字,所以减一。总之只要汉字字符个数能被三整除就不会存在残缺汉字字符的情况 cutNum = byteCnt - 1; } return new String(gbkBytes, 0, cutNum, "gbk"); } } 执行结果: 你好 你好啊he 注意 上面用到的一个String对象方法getbytes();这个方法有不少人存在误解,这个方法有个重载的版本是可以指定字符编码的,并不是只能获取与当前工作空间相对的编码字节数组。另外有人用如下方式将utf-8字符串转换为gbk字符串然后输出: String str = "你好hello"; String str_gbk = new String(str.getBytes("utf-8"), "gbk"); // System.out.println(str_gbk); 然后说中文乱码表示转换成功了,如果我理解无误的话,我觉得上述做法很可笑。上述方法明显是错误的,str.getBytes("utf-8")以utf-8编码规则(一个中文3个字节)获取为byte数组; new String(xxx, "gbk")是将当前符合utf-8编码规则的字节数组以gbk编码规则(一个中文2个字节)存储为字符串,很明显,这样一来必然乱码,然而乱码的同时真的达到转化编码的目的了吗?很明显没有达到,而是进行了一个错误的转换。 以上只是我个人看法,若您在阅读过程中发现问题或您有不同见解,欢迎给我留言。 【原创内容,转载请注明出处】
2017-07-01
Java
一个XML命名空间相关的问题
DOM4j就不介绍了,解析和生成XML文件都很方便。我遇到的问是这样的,博客需要以sitemap的方式向搜索引擎提交url数据,sitemap可以是txt或者xml等格式,这里我用的是xml格式。 看了一下sitemap文件相关说明,里面包含urlset根节点,url节点,lastmod节点,此外还有一些可选节点。结构简单,很快便处理完成了,但是后面Google提醒说sitemap文件中没有包含相应的命名空间,看到提醒之后我简单改了一下,我是这样弄得(部分代码): Element root = document.addElement("urlset"); root.addAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9"); 因为xmlns位置和普通属性在一个位置,所以我想都没想就认为不就添加一个属性吗,完成之后测试了一下,什么玩意,没效果。。。我开始查找错误,以为是哪里写错了,查了两次试了两次,依旧无果。最后还是求助搜索引擎了。一查发现不少人也有这样的困惑,都想当然地这样弄了。问题不是什么大问题,最后按照网上的方法弄好了,并且补全了命名空间。如下: Element root = document.addElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9"); root.addNamespace("xsi","http://www.w3.org/2001/XMLSchema-instance"); root.addAttribute("xsi:schemaLocation","http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"); 总结下来出现这样的问题有两个三个主要原因: (1)自己平时接触XML命名空间挺多,比如安卓开发中的布局等xml中都有涉及,也有涉及自定义命名空间,但是都是手写的。直接用代码生成的还是第一次; (2)对于DOM4j的特殊操作还不熟; (3)有时候做事太想当然了,这习惯得改。。。 当然,这样的问题遇到一次下次就知道怎么弄了,更重要的是其中暴露出的自身的除知识以外的一些问题。记录下此问题,以此告诫自己改掉一些坏习惯。另外,也给可能遇到此问题留个参考吧。
2017-06-30
随笔
静态资源访问404解决办法
在进行web开发的时候用了spring相关框架之后有时候会发现静态资源访问出现404错误,对框架不太熟悉的新人往往会搞得一头雾水(我刚开始学习框架的时候弄得一脸懵X),不用框架还好好的,为啥一用框架就出错呢。经常用框架的人一般不会遇到这个问题。 那么怎么解决呢。先找原因,原因就是静态文件被拦截了,外部不可见,一般都是因为配置mvc-dispatcherServlet拦截“/”才这样的。说白了就是没有针对静态文件作相关配置,原因找到了,接下来就简单了。 这里有两个简单的方法,一个是利用Tomcat的defaultservlet来处理静态资源,一个是直接在spring中配置静态资源映射。 defaultServlet配置 其配置比较简单,但是要注意的是其位置一定要在dispatcherServlet之前,针对自己项目的静态资源合理配置,配置实例如下: <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.xml</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping> 但有时候你发现根目录下有个html文件你想从外部直接访问,于是按照上面的配置将 *.html交给defaultServlet处理了,然后重启Tomcat,杯具来了,所有路径都404了。显然此处就不能这么弄了,这样配置之后影响到springmvc的代理了,所以直接用下面的方法吧。 springmvc中配置静态资源映射 很简单,只需配置mvc:resources标签就行了,比如根目录“/”下的静态文件配置如下: <mvc:resources mapping="/*.xml" location="/"/> <mvc:resources mapping="/*.ico" location="/"/> <mvc:resources mapping="/*.html" location="/"/> <mvc:resources mapping="/*.txt" location="/"/> 同样针对当前项目合理配置静态资源类型即可,而且可以发现配置 html 文件映射也不会出错了,问题迎刃而解。
2017-06-28
Java
JavaScript中的正则表达式
前面说过正则表达式在不同语言平台只是存在一些书写或使用上的细微差别,所以对于JavaScript中的正则表达式无非就几个点需要熟悉一下。 JavaScript中正则表达式的定义 通过new RegExp对象产生,或者直接书写,因为JavaScript中用斜杠包围的式子被认为是正则表达式,所以可以方便地这样定义一个正则表达式: var regex = /\d+/; 涉及的标志 涉及的标志主要有: (1) i : 忽略大小写 (2) g : 设置全局匹配 (3) m : 设置多行匹配 以上标志可以搭配使用, 如全局匹配并忽略大小写可以写为:gi m 标志比较特殊。m标志必须在字符串中含有换行(“\n”或者“\r\n”)并且正则表达式中含有 ^ (字符串开头标志)或者 $ (字符串结束标志)时才有用。通俗点说也就是 m 标志会将换行识别为一行的结束和另一行的开始,而这样识别的条件是字符串含有换行 且正则表达式为^$模式。 不要错误地理解多行匹配,实际上只要正则表达式不是 ^$ 模式的话本身就是支持多行的。比如以下代码, 换行形同摆设: var str = "1aabb\n2aabb\n3aabb"; document.write(str.match(/\d/)); //输出 1a document.write(str.match(/\d/g)); //输出 1a, 2a, 3a RegExp对象的三个方法: (1)compile(): 编译正则表达式 (2)exec(): 匹配字符串中匹配的值,返回找到的值,并确定其位置。 (3)test(): 检索字符串中指定的值。返回 true 或 false。 对于exec()方法,若正则表达式不带 g 标志,则无论执行多少次都只是匹配第一个结果。带上 g 标志则进行依次匹配,即第一次执行匹配第一个结果,第二次执行匹配第二个结果,依此类推。因此多次执行exec()方法一般都是带 g 标志的正则表达式(否则多次执行就没多大意义了)。并且,在得到匹配结果后,还能利用regexp的lastIndex获取当前匹配得到的结果的末尾位置。 对于test()方法,带不带 g 都是一样的,因为返回值为 boolean 类型,只要匹配就返回 true。 与正则表达式挂钩的自然就有String对象的方法: (1)search(): 检索与正则表达式相匹配的值。 (2)match(): 找到一个或多个正则表达式的匹配。 (3)replace(): 替换与正则表达式匹配的子串。 (4)split() : 把字符串分割为字符串数组。 对于search()方法,其不执行全局匹配,它将忽略标志 g。它同时忽略 regexp 的 lastIndex 属性,并且总是从字符串的开始进行检索,也就是说它总是返回 stringObject 的第一个匹配的位置。如果没有找到任何匹配的子串,则返回 -1。由于多匹配返回的是字符串数组,所以想要获取结果在原字符串中的位置利用match()是不太现实的,这里如果有位置方面的需求可以用RegExp的exec()方法。 对于match()方法,其可以匹配一个或多个结果,主要取决于regexp有没有全局匹配标志 g。若没有匹配到结果,方法返回 null。 对于replace()方法,其替换结果也是依赖于标志的,不带标志 g 的话只替换的一个匹配结果,带上 g 的话替换所以匹配的部分。 对于split()方法,g 标志有与没有是一样的。 下面是一个简单的demo,简要说明i,g,m标志的作用。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>RegExpTest</title> <script type="text/javascript"> function run() { var str = "year:2017 month:6 DAY:24"; write("匹配对象: " + str + "<br/>"); var result; var regexp = new RegExp(/[a-z]+/, 'gm'); //var regexp1 = /[a-z]+/g; //与上述写法等效 /* 参数说明: g <----> 即global,全局匹配,会匹配出全部符合的结果 i <----> ignore case, 忽略大小写 gi <----> 上面两者组合 */ write("<br/>exec()方法<br/>"); write("g 和 gi 参数对比大小写忽略测试:<br/><strong>(/[a-z]+/g)</strong>:"); while((result = regexp.exec(str)) !== null){ write(result); write(" " + regexp.lastIndex + ", "); //匹配到的结果的尾部位置 比如 匹配到 year 位置为 4 } var regexp2 = /[a-z]+/gi; write("<br/><strong>(/[a-z]+/gi)</strong>:"); while((result = regexp2.exec(str)) !== null){ write(result); write(" " + regexp2.lastIndex + ", "); } write("<br/><br/>参数 g 效果展示:<br/>"); write("(/\d+/g):"); write(str.match(/\d+/g)); //带 g write("<br/>(/\\d+/):"); write(str.match(/\d+/)); //不带 g write("<br/>不带全局属性 g 的只匹配第一个结果<br/><br/>"); //字符串含有换行并且正则表达式为 ^$ 模式时 m 标志才有用 write("m 标志测试:<br/>"); var str1 = "h1iiiiiiii\r\nh2iiiiiiiii\n\nh3iiiiiiiii"; write(str1.match(/^h\d/g)); write("<br/>"); write(str1.match(/^h\d/m)); write("<br/>"); write(str1.match(/^h\d/gm)); } function write(str) { document.write(str); } run(); </script> </head> <body> </body> </html> 执行结果: 匹配对象: year:2017 month:6 DAY:24 exec()方法 g 和 gi 参数对比大小写忽略测试: (/[a-z]+/g):year 4, month 15, (/[a-z]+/gi):year 4, month 15, DAY 21, 参数 g 效果展示: (/d+/g):2017,6,24 (/\d+/):2017 不带全局属性 g 的只匹配第一个结果 m 标志测试: h1 h1 h1,h2,h3 以上只是我个人的见解,若您在阅读的过程中发现问题或者你有不同的见解,欢迎留言。 【原创内容,转载请注明出处】
2017-06-24
JavaScript
不可不习的利器——正则表达式
正则表达式,又称规则表达式, 英文Regular Expression。可以从字面上理解其大概的意思,描述规则的一种表达式,那么描述什么规则呢 描述字符串构成规则的一种表达式。这么理解也就可以了。 本片文章并不打算长篇介绍和总结正则表达式相关用法。只是简要记录一下我觉得需要注意的一些点,如果你想要系统地学习正则表达式,你可以看一些书籍。或者找一些写的比较全的博文看一下。这里我推荐我觉得写的比较好的两篇文章,读完这两篇文章,写几个例子自己试一下,也就可以入门了。 文章一(强烈推荐):正则表达式语法及入门教程 文章二:Pattern类与Matcher类详解 文章三:Java正则表达式的语法与示例 【值得注意的一点,参考文章可能针对不同语言作描述,不同语言正则表达式书写可能有些许差异,但是思想是一样的,或者说你能立刻理解这些差异,例如反斜杠在Java中的特殊性,导致在书写正则表达式时要做转义:\b ----> \\b 等】 首先,必须记住的一些元字符(随便怎么叫,记住就行) . 匹配除换行符以外的任意字符 ^ 匹配字符串的开始 $ 匹配字符串的结束 \b 匹配单词的开始或结束 \d 匹配数字 等同 [0-9] \s 匹配任意的空白符 \w 匹配字母或数字或下划线或汉字 常见限定符 * 重复 0 次或更多次 + 重复 1 次或更多次 重复 0 次或 1 次 {n} 重复 n 次 {n, } 重复 n 次或更多次 {n, m} 重复 n 到 m 次 常用反义代码 \B 匹配不是单词开头或结尾的位置 \S 匹配任意不是空白符的字符 \D 匹配任意非数字的字符 \W 匹配任意不是字母,数字,下划线,汉字的字符 [^x] 匹配任意不是 x 的字符 [^xyz] 匹配除了xyz这几个字符之外的字符 还有一个分组概念,用括号()区分: 组 0 其实是整个正则表达式。组 1 及其之后的按括号匹配,如正则表达式形式: A(B(C)(DE(F)))G group 0 : A(B(C)(DE(F)))G group 1 : (B(C)(DE(F))) group 2 : (C) group 3 : (DE(F)) group 4 : (F) 当然,如果你直接在整个正则表达式套上括号:(A(B(C)(DE(F)))G), 那么 group 1 就是(A(B(C)(DE(F)))G), 等同于 group 0。 说了这些,说说正则表达式常用的地方: (1)、字符串格式检测(匹配) (matches) (2)、字符串分割 (split) (3)、获取字符串 (find) (4)、字符串替换 (replaceAll) 写个简单的demo测试一下(2,3,4): package org.devsong; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegExp { public static void main(String[] args) { String str = "Ab1.Cd2.Ef3.Gh"; /****************字符串分割***************/ String[] strArr = str.split("\\d\\."); for(String s : strArr) { System.out.print(s + " "); } System.out.println(""); /****************获取字符串***************/ Pattern pattern = Pattern.compile("(\\w(\\d\\.)\\w)"); //编译regex:(\\w(\\d\\.)\\w) Matcher matcher = pattern.matcher(str); while(matcher.find()) { //group: -整个正则表达式- group 0 对应的是整个正则表达式的匹配结果, // -(\\w(\\d\\.)\\w)- group 1 代表的是第 一 对括号中正则表达式的匹配结果, // -(\\d\\.)- group 2 代表的是第 二 对括号中正则表达式的匹配结果,... ... 依此类推 //* 由于本例中整个正则表达式外围显式地套了一层括号,所以group 0 和group 1 输出结果是一致的 //* 另外,group 2 在此例中对应的就是 \\d\\. 匹配的内容 System.out.print(matcher.group(2) + " "); //group(0)输出: b1.C d2.E f3.G //group(1)输出: b1.C d2.E f3.G //group(2)输出: 1. 2. 3. } System.out.println(""); /****************字符串替换***************/ System.out.println(str.replaceAll("\\d\\.", "-")); } } 以上代码输出结果: Ab Cd Ef Gh 1. 2. 3. Ab-Cd-Ef-Gh 以上只说了一些常用的基本点,还有很多特性没有提到。上面提到的文章都做了比较详细的解说,特别是第一篇,强烈推荐。
2017-06-22
Java
指针操作时得注意的一些要点
通俗点讲,指针是内存地址的一个形象化描述,通过其本身可以访问以它自身为地址的内存单元。为方便理解,你可以将指针简单理解为指向某个内存空间的对象。那么,既然指针指向的是某个内存单元,通过它能够访问该内存空间也是自然的。 下面说一下几个与指针相关的值得注意的点 (1)指针的大小 (2)指针的自加和自减 (3)指针大小和一块内存的大小 指针的大小 指针的大小对于刚接触指针的人来说是一个很容易令人困惑的问题,其实指针的大小很容易理解。上面已经说过,指针及内存地址,那么容易想到在32位运行环境(32位编译器)下,地址的大小为4个字节,所以这时指针的大小为4个字节(与寻址空间相对应)。64位运行环境(64位编译器)下,地址大小为8个字节(与寻址空间相对应),这时指针大小为8个字节。 说到这里,或许你还半信半疑。不要紧,敲个简单的例子就行了。以下是测试代码: #include <iostream> using namespace std; int main(int argc, char const *argv[]) { cout << sizeof(char *) << " " << sizeof(int *) << " " << sizeof(long *) << endl; return 0; } 在32位环境下你将看到如下输出: 4 4 4 在64位环境下,你将看到以下输出: 8 8 8 【这里简单说一下,如果你要测试以上代码,并且你当前的系统是64位的,那么你可以借助一些工具很轻松地完成32位和64位环境下的测试,比如Vsual Studio,你可以选择运行环境。如果你没有这样重量级的IDE,那么你可以借助Cygwin这样的工具,下载一个32位版本测试32位环境。如果你当前是32位的系统,那么你就找台64位系统的电脑测试吧】 看完了上面的东西,下面说说关于指针大小的一些理解误区。很多初学者常犯的错误是: (1)错误认为指针的大小取决于前面类型限定名的大小。一定要谨记定义指针的类型限定名只是影响指针指向的内存单元的数据长度,与指针的大小无关。怎么理解呢?看下图: 有这样的语句: #include <iostream> using namespace std; int main(int argc, char const *argv[]) { int x = 0x12345678; //小端序: 78 56 34 12 cout << hex << (int)*((char*)&x) << endl; //输出 56 由于指针强转位char*, 所以访问数据长度为一个字节,即第一个字节 cout << hex << (int)*((short*)&x) << endl; //输出 5678 由于指针强转位short*, 所以访问数据长度为两个字节,即第一和第二个字节 return 0; } 以上程序展示之前定义时限定符的作用效果。有关端序的内容这里不再赘述,请查看上一篇文章, 点此打开。 指针的自加和自减 指针的自加自减考虑的点是一样的,或者说指针加减(如: p = p + 2)考虑的点是一样的,所以这里只讨论指针的加操作。 指针的加减操作每次的步长取决于定义指针时的类型限定。看下面的代码: #include <stdio.h> int main(int argc, char const *argv[]) { int x = 0x12345678; //大部分常见的CPU都为小端序,包括当前测试用机:故内存中十六进制形式: 78 56 34 12 char *p = (char*)&x; printf("0x%x\n", (int)*(++p)); //输出 0x56 ++p 一次跳了一个字节,即 sizeof(char) 个字节 short *p1 = (short*)&x; printf("0x%x\n", (int)*((char*)(++p1))); //输出 0x34 ++p1 一次跳了两个字节, 即 sizeof(short) 个字节 return 0; } 以上代码跑得很顺利,然而我们好像忘记了一个东西, 那就是 void。神一般地存在,用好了无所不能,用不好满地大坑。我们也常常称void*型指针为万能指针。它特殊在哪呢?我们考虑一下对其进行加减操作的步长是多少呢?将上面的代码简单修改一下: #include <stdio.h> int main(int argc, char const *argv[]) { int x = 0x12345678; //大部分常见的CPU都为小端序,包括当前测试用机:故内存中十六进制形式: 78 56 34 12 void *p = &x; //变为void printf("%x\n", (int)*((char*)(++p))); //输出 56 ++p 一次跳了一个字节,即 sizeof(void) 个字节 printf("%d\n", sizeof(void)); //输出 1 void *p1 = &x; //变为void printf("%x\n", (int)*((char*)(p1 + 1))); //输出 56 ++p1 一次跳了一个字节, 即 sizeof(void) 个字节 return 0; } gcc编译运行输出结果: 56 1 56 貌似是正常的,其实不然。说void坑多肯定不是这么简单就完事的。其实void又被称为无类型或者不完整类型,那么它的大小就是一个谜,上述gcc编译运行输出其大小为 1, 那么它的大小一定是1吗?换个编译器试试,先将其他注释,只测试 printf("%d\n", sizeof(void)); 用VisualStudio编译运行。结果输出为0。并且IDE会给出警告:不允许使用不完整类型。 接下来我们去掉其他注释,将代码用VisualStudio编译,编译失败,显示错误:void*:未知大小,表达式必须指向完整类型的指针。也就是说对void*型的指针进行加减操作是不合法的。 上述操作都是在C语言层面进行的,接下来换到C++:(为方便,这里就不改为C++标准输出了,只看编译结果)用 g++ 编译, 编译输出如下: 警告内容 (1)sizeof()不能用于void (2)ISO C++ 不允许对类型为‘void*’的指针自增 [-Wpointer-arith] (3)‘void *’型指针用在了算术表达式中 [-Wpointer-arith] 虽然出现了警告,但是还是通过编译并正确运行。说了那么多,只为说明一点,如果你没有十足的把握避开那些坑,就不要用void*,哪怕其名为万能指针(开个玩笑 :))。总之,少用即可。 指针大小和一块内存的大小 这里不说啥高端的东西,只说一些常见的“考试题”。结合指针大小可能会出现以下题: #include <stdio.h> #include <stdlib.h> /*************************************** 请给出以下程序的执行结果。 ***************************************/ int main(int argc, char const *argv[]) { char *p = (char *)malloc(100); long *p1 = (long *)malloc(100 * sizeof(long)); char arr[200]; printf("%d %d %d\n", sizeof(p), sizeof(p1), sizeof(arr)); return 0; } 毫无疑问: 32位运行环境: 4 4 200 64位运行环境: 8 8 200 前两个sizeof()输出的都是指针大小(C++中int *p = new int[10] 亦是同等效果),而最后一个输出的是一个内存块的大小(字节数)。可见,虽然在传参的过程中传递的相当于指向数组首元素的指针,但是数组名并不是完全等同于指针,数组名总是指向数组首元素,记录数组内存开始的地方, 不能随意变更,这也就引出了另外一点:数组名不能作加减操作,如果有这方面的需求,可以用指针指向数组,然后利用指针完成相关操作。与数组相关的更多内容,可以查找其他文献看一下。 以上内容只是我个人的见解,如果您在阅读的过程中发现错误,欢迎留言。 【原创内容,转载请注明出处】
2017-06-18
C/C++
大端序和小端序
字节序,我们习惯上称其为端序,又称尾序, 通俗点将就是内存中存放数据的字节顺序。那么很容易想到有两种: (1)低地址存放数据的低位,高地址存放数据的高位。这就是所谓的小端序(我习惯这样记忆: 低低 ---> 小) (2)低地址存放数据的高位,高地址存放数据的低位。这就是所谓的大端序。 看上面的文字描述或许不够直观,还容易绕晕,直接看个图示: 看完上面的图,对于端序,你应该有个初步的认识了。也可以自己敲个程序看一下数据在内存中的存储情况。 如下测试代码: #include <iostream> using namespace std; int main(){ int a = 0x12345678; int *p = &a; return 0; } 借助IDE,在以上代码打个断点,然后打开内存窗口,查看内存中 a 的存储形式。如下是某次运行内存中的情况【红框部分(十六进制查看)存储的就是 a 值】: 自己动手操作之后你应该更加理解端序这个概念了,那么比较常见的问题是如何通过程序判断当前平台是大端序还小端序呢?理解原理了就比较简单了,只需读取并对比数据即可判断,下面给出一个简单示例: #include <iostream> using namespace std; int main(){ int a = 1; //读取 a的地址空间 的第一个字节的数据和a的低位数据作一个对比即可 cout << ((((int)*((char*)&a)) == 1) "小端序" : "大端序") << endl; return 0; } 总结 大小端序是硬件层面的东西,很多人平时可能接触不到,当然了解一下还是好的,做嵌入式的话不用多说,这个是必须掌握的基本知识,以上内容只代表我个人的观点,若您在阅读过程中发现错误,欢迎留言指正。
2017-06-16
嵌入式
一个久远的项目:基于Qt的简单计算器
以前刚接触QT时做的一个计算器程序,只是实现了一些基本的运算,由于是初期摸索着做的,代码结构很混乱,后期也没有继续维护,但这是我的第一个QT项目,还是放到这里做个纪念吧。也可以给初入Qt的朋友做个参考。 这里提供一下源码及可执行文件: 工程下载(开发工具VS 2015):点击下载 可执行文件(安装即可运行):点击下载 当然,你也可以在github上下载源码:点击跳转(请无视我空白的github提交记录, 尴尬。。。) 效果展示 功能一:基本计算 功能二:学分绩点计算(请无视,计算方法不同)
2017-06-13
C/C++
Java保留关键字之instanceof
instanceof 是Java的一个保留关键字,作用是判断其左边对象是否为其右边类的实例,返回boolean类型的数据。可以用来判断继承中的子类的实例是否为父类的实现。 格式: boolean res = object instanceof class 编译报错的几种情况: (1)明确知道没有继承关系的类 (2)最终类对象 --对于-- 未实现的接口 返回true的情况: (1)子类对象 --对于-- 父类 (2)接口实现类对象 --对于-- 接口 (3)当前类对象 --对于-- 当前类 返回false的情况: (1)父类对象 --对于-- 子类 (2)普通类对象 --对于-- 未实现的接口 (3)null -- 对于-- 任意类 以上为自己测试总结的结果,能力有限,可能不全。欢迎留言补充。 还是那句话,看下简单的示例代码: package org.devsong; import java.util.Map; interface InterOne{} class A{} class B extends A{} class C implements InterOne{} final class D{} final class E implements InterOne{} public class InstanceOfDemo { public static void main(String[] args) { A a = new A(); A ab = new B(); B b = new B(); InterOne inter = new C(); C c = new C(); D d = new D(); E e = new E(); /** * 子 类 对 象 instanceof 父 类 ---> true * 接口实现类对象 instanceof 接 口 ---> true * 当 前 类 对 象 instanceof 当前类 ---> true * 即以下语句执行结果为 true */ System.out.println(a instanceof A); System.out.println(ab instanceof B); System.out.println(ab instanceof A); System.out.println(b instanceof A); System.out.println(inter instanceof InterOne); System.out.println(c instanceof InterOne); /** * 父类对象 instanceof 子 类 ---> false * null instanceof 任意类 ---> false */ System.out.println(a instanceof B); //false System.out.println(null instanceof A); //false /** * 没有继承关系的两个类:类1对象 instanceof 类2 ---> 编译报错: Incompatible conditional operand types C and A */ System.out.println(c instanceof A); //报错 /** * 特别注意一下接口的情况 * 没有 “实现关系” 的普通类可以用 instanceof(尽管好像没什么意义) ---> false * 没有 “实现关系” 的最终类不可以用 instanceof, 否则编译报错 :Incompatible conditional operand types D and Map */ System.out.println(a instanceof InterOne); //false System.out.println(a instanceof Map); //false System.out.println(d instanceof InterOne); //编译报错 注意观察class A 与 class D, A是一个普通类, D用final修饰, 是一个最终类 System.out.println(d instanceof Map); //编译报错 System.out.println(e instanceof InterOne); //类和接口有实现关系的话 ---> true } } 以上只是个人总结结果,个人能力有限,理解尚浅,或许存在不全或有误的地方,若您发现问题,欢迎留言。
2017-06-09
Java
CKEditor配置使用
涉及到网页文本编辑那就少不了一款好用的富文本编辑器。网上一搜可以找到很多富文本编辑器。这里我整理了一些: UEditor http://ueditor.baidu.com/website/ KindEditor http://kindeditor.net/ TinyMCE https://www.tinymce.com/ Simditor http://simditor.tower.im/ Wangeditor http://www.wangeditor.com/ xhEditor http://xheditor.com/ QuillEditor https://quilljs.com/ (实测需要梯子, 不过好东西用下梯子又何妨 :) ) 上面以及给出的列表已经很齐全了,每个编辑器我都体验了一下DEMO【每个编辑器官网都有DEMO】,总体来说还是比较好用的。有些讲究大而全,有些讲究小而美。也就是说,功能齐全一般意味着大体积,功能简洁自然对应小体积。至于选择大而全还是小而美就得个人看喜好及需求了。不过上面说到的功能少并不是指缺少很多功能,总之常用的自然是有的。 那么讲了那么多还是回归今天的主角吧。CKEditor https://ckeditor.com/ 我觉得还是比较好用的,配置也挺方便。之所以选择这个,并不是说上面推荐的不好,这个完全看个人喜好了。只是觉得这个用起来比较顺手,说到顺手你就应该明白了,这种东西真是因人而异。 要使用CKEditor,首先需要下载资源包,打开其官网,然后点击Download,之后会跳转到一个版本选择页面,上面会有几个选项,类似下图: 默认会推荐你下载标准版,但是经过我的测试,标准版默认是不集成文字颜色组件的,所以有文字颜色需求的慎下。基础版自然不必说。那么想要文字颜色之类的相对高级的东西该怎么办呢?这里有两条路:(1)选择下载完整版,(2)选择自己搭配。 如果你的服务器宽带足够快,或者你不在乎加载时间等等,那么你可以下载完整版(注意:完整版并不是指包含所有插件,你可以点击 Compare packages 查看三个版本的不同。如果有需求的话,许多插件还是需要自己下载的)。 接下来说说剩下的自由定制版,这个版本比较自由,想要啥功能完全在个人,这也是我比较推荐的方式。点击Online builder,然后会跳转到builder页面,主要有三步: 第一步:选择在哪个版本之上进行构建(Basic, Standard, Full)。我习惯选基础版。 第二步:选择所需的功能(插件)及皮肤。功能选择和删除只需选中后点击中间的箭头即可。 第三步:选择语言。这一步不必多解释(默认是英文,且英文必选)。 另外,自由定制也可根据已经存在的配置文件进行。我挑选了几个我认为比较常用的功能,这里分享一下我的配置文件:点击下载 当然,可以对我提供的配置列表进行定制,比如我没有将Markdown插件集成在内,需要的可以自行在官网搜索下载。 只需在第一步下面选择 “Upload build-config.js” 上传配置文件即可。如下: 完成定制就可以下载了。下载完成后会得到一个压缩包,解压会得到如下目录: 我们只需要关心config.js,我们进行配置都在里面完成。至于builder-config.js只是对当前定制信息的一个存储,方便再次下载,修改其并不会影响当前编辑器。 另外,我上面提供的配置文件其中包含了代码高亮的插件,其依赖于highlight.js。可以打开如下目录:ckeditor\plugins\codesnippet\lib\highlight。里面便是highlight相关的东西。之所以要提到这个东西,是因为其集成的highlight.js可能不是最新版,一些主题并不能在里面找到,所以我们可以去highlight.js官网下载相关资源(css主题资源)放入Style文件夹即可。 至于配置,我提供几点推荐配置的。如下: //禁止大小调整 config.resize_enabled = false; //宽高设置 config.width=900; config.height=900; //设置换行模式,不在此模式可能出现按一下空格多一个空行的情况 config.enterMode = CKEDITOR.ENTER_BR; //代码高亮主题设置。 名称即为highlight/styles里面的主题名 config.codeSnippet_theme = 'gruvbox-dark'; 将以上代码添加至config.js即可。 做完上面的工作,接下来就是实测了: 前端测试代码: <!DOCTYPE html> <html> <head> <title>test</title> <script type="text/javascript" src="./ckeditor/ckeditor.js"></script> </head> <body> <textarea class="ckeditor" id="ckeditor"></textarea> </body> </html> 值得注意的点 有时候需要动态地加载编辑器,比如AJAX刷新局部页面需要一个编辑器,那么此时就需要我们手动调用替换方法:在DOM元素加载完之后调用如下语句。“ckeditor”为要渲染的textarea的id。【替换有内容的textarea同样如此操作,即先把内容放入原生textarea, 然后调用如下方法即可】 CKEDITOR.replace("ckeditor"); 在获取编辑器内容时,如果不采用传统的表单提交方式,而是用js提取的话,就需要如下操作; CKEDITOR.instances.ckeditor.getData() 同样,这里的ckeditor是textarea的id。 以上只是简单的配置教程,一篇笔记是无法将清楚地讲解清楚的,并且很多东西我还没有探索到,不过好在官网有详尽的文档,相信你能在那找到你想要的。若您对以上内容有不同看法,欢迎给我留言,共同学习进步!
2017-06-06
HTML/CSS
线程相关方法之yield方法
yield方法是Thread类的一个静态方法,这个方法是困惑了我很久的一个方法, 为什么这么说呢?首先我是比较喜欢逛一些IT社区的,在上面也看了一些关于多线程相关的东西,自然这个方法的相关说明和演示也看过不少,但问题就在这,当我自己准备写关于这个方法的学习笔记时,我开始困惑了。 因为网上不少博客对yield()方法的说明都是“调用此方法的线程会放弃当前CPU的使用权,返回可运行(就绪状态),然后调度程序可能立马选择当前线程,又开始执行。所以调用该方法可能不会有效果”。 因为网上有大量的博客都是这样说的,所以我一度认为就是这样的,知道我看了JDK源码里面的注释: /** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield(); 多余的我们先不看,只看第一条即可:A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.,翻译过来就是:提示调度器当前线程愿意放弃持有的CPU资源,调度器可以自由忽略这个提示。 那么问题来了,放弃和愿意放弃是截然不同的两个概念。这正是我困惑的地方,网上的描述都是放弃自己持有的CPU资源, 回到就绪状态。我该选择那个观点?最终我选择相信源码里面的注释。 用自己的话描述上面注释中那一句简单的观点就是:调用yield()方法只是暗示调度器当前线程愿意放弃持有的CPU资源,调度器完全可以不理会这个暗示,以致啥都不会发生, 也就是说属于调度器的权利还是在调度器那里, 并不是一调用这个方法就立即让出资源,然后等待调度器在此选中,说白了放不放弃CPU资源自己说了不算,得听调度器的。这让我联想到了System.gc()方法,只是对垃圾回收的一个建议。 说了那么多,感觉少点代码,有点空。但是如果上面我理解的是正确的,那么代码示例就没什么卵用(当然,就算我理解的是错误的,写一个demo也不太容易看出让步的现象)。 不过就算这样,我还是贴出个没什么卵用的demo,大家看看就好。结果啥的我就不贴了,毕竟贴结果同样或者说更加无卵用。。。 package org.devsong.test; /** *各位看看就好 */ public class ThreadTest { public static void main(String[] args) throws InterruptedException { new Thread(new MyRunnable("A")).start(); new Thread(new MyRunnable("B")).start(); } } class MyRunnable implements Runnable { private String str = ""; public MyRunnable(String str) { this.str = str; } @Override public void run() { int i = 0; while (i++ < 20) { System.out.println(str + " " + i); if(i == 10) { Thread.yield(); } } } } 以上只是我个人的看法,若您有好的想法,欢迎给我留言。
2017-06-02
Java
线程相关方法之wait方法
说wait是线程相关方法其实是有点问题的。因为wait()方法实际是属于Object的方法,也就是说所有对象都有wait()方法。与wait()方法相关的,自然就想到了notify和notifyAll(), 同样这二者也隶属与Object,自然也是所有对象都有的。想想还是很有意思的。 下面说说和线程相关的用法。 一个线程调用wait()方法后,会进入等待队列(等待状态),进入这个状态是不能自动唤醒的,必须靠其他线程调用notify方法或者notifyAll()方法才可能被唤醒,之所以说可能,是因为调用notify是随机唤醒一个等待状态的线程,并不能保证唤醒的就是我们需要的线程。所以一般使用notifyAll(),notifyAll()方法会一次性唤醒所有在等待状态的线程,等待状态的线程被唤醒后会进入锁池,等待获得锁标记。这里就是之前说过的线程状态相关的东西了。 上面说到从等待队列出来的线程会进入锁池等待获取锁标记,那么就又引出一点知识:wait()方法的调用会释放掉当前线程持有的所有资源,包括持有的锁。 另外,由于wait()、notify()、notifyAll()方法会对对象的锁标记进行操作,所以这三个方法只能在synchronized函数或者synchronized同步块中使用。否则会抛出IllegalMonitorStateException异常。 此外,wait()方法还有另外两个重载版本:wait(long timeout); wait(long timeout, int nanos);只是对于第二个重载的方法,里面的 nanos 参数的处理仅仅是: if (nanos > 0) { timeout++; } 不太懂 nanos 存在的意义。如果您恰巧知道其中的奥秘,欢迎留言 :)。 可以结合前面几篇博客来看。点击文章下方 多线程 标签即可获取相关笔记列表。
2017-06-01
Java
1
2
3
...
5
6
7
8
9
涂鸦
热门
CSS hover更改其他元素属性
12140
JPEGView--一个好用的图片查看和编辑软件
11157
MySQL 8.0 java.sql.SQLException Unknown system variable 'query_cache_size'
9795
Chrome浏览器暗色模式
8694
intelliJ IDEA Java注释模板配置
8683
Maven 和 Gradle 国内代理配置
8088
android.database.sqlite.SQLiteException:AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY (code 1)
7993
安卓刷机双清recovery报错 Failed to mount '/data' (invalid argument)
6890
Tomcat响应流程及Servlet的执行流程
6392
一个eclipse配置tomcat容易困扰新手的问题
6168
分类
Java
(43)
Android
(9)
C/C++
(11)
嵌入式
(2)
数据库
(8)
PHP
(1)
JavaScript
(7)
HTML/CSS
(7)
随笔
(8)
实用工具
(10)
时光记
(8)
Linux
(11)
Windows
(2)
日常娱乐
(2)
数码
(1)
每日资讯
(0)
最新留言
LuckyX
Aug 18, 2021.
有用!红米note5A 成功,谢谢楼主
嗯
June 23, 2021.
12
极客
Apr 13, 2021.
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As range, Cancel As Boolean) Dim min As Double Dim max As Double Dim range As Double max = ActiveSheet.range(Target.Address).Offset(11, -1).Value min = ActiveSheet.range(Target.Address).Offset(12, -1).Value range = ActiveSheet.range(Target.Address).Offset(13, -1).Value For i = 0 To 10 ActiveSheet.range(Target.Address).Offset(i, 0).Value = (max - ActiveSheet.range(Target.Address).Offset(i, -1).Value) / range Next i End Sub
招投标
Jan 06, 2021.
这个确实很刚需了,用的时候比较多
招投标
Jan 06, 2021.
下次拿家里的安卓机试一试,还可以偶尔用下
招投标
Jan 06, 2021.
感谢大佬的分享,支持一下
招投标
Jan 06, 2021.
文章写的不粗哦,赞一个
月雅
Sept 17, 2020.
大佬教我学编程.。。。。。
小曾曾的博客(www.xiaozeng.cc)
June 14, 2020.
抱歉,因为个人原因,本站暂停运营。
小曾曾的博客(www.xiaozeng.cc)
Apr 29, 2020.
小曾曾的博客(www.xiaozeng.cc)已添加贵站链接,希望贵站能添加。
友链
帅大叔的博客
DevSONG
张宇童-前沿技术博客
逆风的小窝