vim学习笔记

作者: 分类: linux 时间: 2016-02-17 评论: 暂无评论

模式:

普通模式:默认进入
插入模式:i(插入) 或 a(追加)
可视模式:
选择模式:
命令行模式: 输入:接后续命令
Ex模式: 输入:visual 一次执行多条命令

游标移动:

h左 l右 j下 k上 w移动到下个单词 b移动到上个单词

nG(n Shift+g) 游标移动到第 n 行(:set nu显示行号)
gg 游标移动到到第一行
G(Shift+g) 到最后一行
w 到下一个单词的开头
e 到下一个单词的结尾
b 到前一个单词的开头
ge 到前一个单词的结尾
0或^ 到行头
$ 到行尾
f<字母> 向后搜索<字母>并跳转到第一个匹配的位置(非常实用)
F<字母> 向前搜索<字母>并跳转到第一个匹配的位置
t<字母> 向后搜索<字母>并跳转到第一个匹配位置之前的一个字母(不常用)
T<字母> 向前搜索<字母>并跳转到第一个匹配位置之后的一个字母(不常用)

插入位置:

i 在光标后插入
I 在行首插入
A 在行末插入
a 在光标后插入
o 在当前行后插入一行新的
O 在当前行前插入一行新的
cw 替换从光标所在位置后到一个单词结尾的字符

退出方式:

:q! 强制退出,不保存
:q 退出
:wq! 强制保存并退出
:w <文件路径> 另存为
:saveas 文件路径 另存为
:x 保存并退出
:wq 保存并退出

删除文本:

x 删除游标所在的字符
X 删除游标所在前一个字符
Delete 同x
dd 删除整行
dw 删除一个单词(不适用中文)
d$或D 删除至行尾
d^ 删除至行首
dG 删除到文档结尾处
d1G 删至文档首部
{数字}dd 一次删除多行

重复命令

普通模式输入N,N表示重复后面的次数
3dd 删除3行
5x 删除5字符
d2w 连续删除2个单词

复制粘贴剪切

普通模式中
使用y复制:
yy复制游标所在的整行(3yy表示复制3行)
y^ 复制至行首,或y0。不含光标所在处字符。
y$ 复制至行尾。含光所在处字符。
yw 复制一个单词。
y2w 复制两个单词。
yG 复制至文本末。
y1G 复制至文本开头。

使用p粘贴:
p(小写)代表粘贴至光标后(下)
P(大写)代表粘贴至光标前(上)

dd删除命令就是剪切
ddp 可以实现交换上下行

替换和撤销(Undo)

r+<待替换字母> 将游标所在字母替换为指定字母
R 连续替换,直到按下Esc
cc 替换整行,即删除游标所在行,并进入插入模式
cw 替换一个单词,即删除一个单词,并进入插入模式
C(大写) 替换游标以后至行末
~ 反转游标所在字母大小写
u{n} 撤销一次或n次操作
U(大写) 撤销当前行的所有修改
Ctrl+r redo,即撤销undo的操作

快速缩进 调整文本位置

整行向右缩进
<< 整行向左缩进
:set shiftwidth? 查看缩进字符的设定
:set shiftwidth=数字 设置缩进字符数量

:ce(center)命令使本行内容居中
:ri(right)命令使本行文本靠右
:ri(right)命令使本行文本靠右

快速查找

/{字符} 查找字符串
?{字符} 向上查找字符串tcp
n查找下一个
N查找上一个

*寻找游标所在处的单词
#同上,但 * 是向前(上)找,#则是向后(下)找
g*同* ,但部分符合该单词即可
g#同# ,但部分符合该单词即可

读懂程序里的--时间

作者: 分类: 工作笔记 时间: 2015-09-27 评论: 暂无评论

题外话,祝各位程序猿们中秋佳节快乐~~~O(∩_∩)O~。

追本溯源

历史

GMT

百度百科:格林威治时间

UTC

百度百科:协调世界时

两者比较:

GMT:最初确立的世界标准时间,名字由来是因为英国的皇家格林尼治天文台而得名,因为本初子午线被定义在通过那里的经线。自1924年2月5日开始,格林尼治天文台每隔一小时会向全世界发放调时信息。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此,格林尼治时间已经不再被作为标准时间使用。新的标准时间,是由原子钟报时的协调世界时(UTC)。--百度百科

UTC:是目前使用的世界标准时间,其以原子时秒长为基础,在时刻上尽量接近于格林尼治平时。国际原子时的误差为每日数纳秒,而世界时的误差为每日数毫秒。对于这种情况,一种称为协调世界时的折衷时标于1972年面世。为确保协调世界时与世界时相差不会超过0.9秒,在有需要的情况下会在协调世界时内加上正或负闰秒。因此协调世界时与国际原子时之间会出现若干整数秒的差别。位于巴黎的国际地球自转事务中央局负责决定何时加入闰秒。 --维基百科

这里要提下有趣的事情是,因为UTC闰秒的缘故,java中的秒可能为60或61,但java文档中有说这个是依据计算机的环境,无法保证准确性囧。
根据这两个比较,GMT和UTC在正常生活中可以指同一个时间,除非你做的东西对时间的准确性极为敏感。

一些重要概念:

时区

1884年国际经线会议规定,全球按经度分为24个时区,每区各占经度15°。
以本初子午线为中央经线的时区为零时区,由零时区向东、西各分12区,东、西12区都是半时区,共同使用180°经线的地方时。

CST :China Standard Time UTC+8:00 中国标准时间(北京时间),在东八区

俺们这块的时区就是 UTC +8 ,相当于我们国家的使用的时间比世界协调时间快了8小时。

unix时间戳

是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
这里在放一个链接,关于为什么都要从1970年算起,以前也真是懒的思考这个问题,拿来就用,以后真的需要做什么事情都先问个为什么了。O(∩_∩)O哈哈~
计算机时间、unix时间、linux时间、java时间为何以1970年1月1日为原点?从1970年1月1日开始计算?
百度百科:unix时间戳

java中使用

时间类 timeStamp date Calendar


    long millisTime = System.currentTimeMillis();
    Timestamp timestamp = new Timestamp(millisTime);
    Time time = new Time(millisTime);
    Date date = new Date(millisTime);
    Calendar calendar = Calendar.getInstance();
    
    System.out.println(time.getTime());
    System.out.println(date.getTime());
    System.out.println(timestamp.getTime());
    System.out.println(calendar.getTimeInMillis());

以上构建之后,输出的都是同样的值,因为前三个是返回是距离January 1, 1970, 00:00:00 GMT的毫秒,而calendar返回的是the current time as UTC milliseconds from the epoch。上面说了GMT和UTC可以看成指同一个时间。所以值就是一样的了。一般网络通信传输时间都是先转成毫秒之后再转回来,因为这里面不会包含时区,这样才是准确的。

在java程序中一般都会使用Calendar因为其他几个类里面的方法几乎都不建议使用了,Calendar中提供了丰富的api,各位程序猿们使用的时候可以自行查找。

    Calendar calendar = Calendar.getInstance();
    //获取所有时区信息
    TimeZone.getAvailableIDs();
    //设置一个时区
    calendar.setTimeZone(TimeZone.getTimeZone("GMT+13"));
    //打印该时区目前小时时间,24制
    System.out.println(calendar.get(calendar.HOUR_OF_DAY));

格式化时间显示类SimpleDateFormat

顺带再提下Java的SimpleDateFormat类,用来把时间格式化显示的,显示的样式很丰富,看下列表格列举的。

|Letter | Date or Time Component| Presentation | Examples|
|----- |----- |---- |---------|
|G |Era designator | Text | AD |
|y | Year | Year | 1996; 96|
|M |Month in year | Month | July; Jul; 07|
|w |Week in year | Number | 27
|W |Week in month | Number | 2
|D |Day in year | Number | 189
|d |Day in month | Number | 10
|F |Day of week in month | Number | 2
|E |Day in week | Text | Tuesday; Tue
|a |Am/pm marker | Text | PM
|H |Hour in day (0-23) | Number | 0
|k |Hour in day (1-24) |Number | 24
|K |Hour in am/pm (0-11) |Number | 0
|h |Hour in am/pm (1-12) |Number | 12
|m |Minute in hour |Number | 30
|s |Second in minute |Number | 55
|S |Millisecond |Number | 978
|z |Time zone |General time zone| Pacific Standard Time; PST; GMT-08:00
|Z |Time zone |RFC 822 time zone| -0800

下面这个方法是我在这里How to convert a String to a Date using SimpleDateFormat?看到的。
相比惯例的格式化时间方法,是把每种格式类型都定义好之后再用分别的方法调用,这个写成了一个统一的方法,虽然效率上没有改进,但是这种写法还是对可阅读性有了不少的提高啊。


/**
     * Format a time from a given format to given target format
     * 
     * @param inputFormat
     * @param inputTimeStamp
     * @param outputFormat
     * @return
     * @throws ParseException
     */
    private static String TimeStampConverter(final String inputFormat,
            String inputTimeStamp, final String outputFormat)
            throws ParseException {
        return new SimpleDateFormat(outputFormat).format(new SimpleDateFormat(
                inputFormat).parse(inputTimeStamp));
    }

头痛的mysql中时间的使用

各有千秋

我们来一一介绍下mysql中的时间,
DATE
显示的格式为YYYY-MM-DD 范围从 '1000-01-01' 到 '9999-12-31' 占用3个字节

DATETIME
显示的格式为YYYY-MM-DD HH:MM:SS 范围从'1000-01-01 00:00:00' 到'9999-12-31 23:59:59' 占用8个字节

TIMESTAMP
显示的格式YYYY-MM-DD HH:MM:SS 范围从'1970-01-01 00:00:01'到 '2038-01-19 03:14:07' 占用4个字节 相信各位如果读了上面unix时间戳的来历,应该明白为什么占用4个字节。

区别:

  1. 首先要提的就是datetimetimestamp之间最大的不同就是在于,timestamp随着时区的变化而变化,默认会根据系统的时区设置来存储时间,打个比方,如果你在东八区存储一个时间,之后把数据库系统设置另一个时区则取出来的值发生变化。如果你存储数据的库分布在不同的时区时你就要考虑这种情况,而采用datetime,因为它是不受时区影响的。
  2. 三种时间格式的非法值都会变成'0000-00-00' 或 '0000-00-00 00:00:00'
  3. timestamp提供了每次数据更新时,自动更新为现有的时间功能,这个功能比较有用。
  4. date会自动处理一些格式比如'10:11:12'会理解成'2010-11-12',年值在'00-69'mysql会认为是'2000-2269',年值在'70-99'mysql会认为'1970-1999'话说mysql还算智能哈。

参考文章:
The DATE, DATETIME, and TIMESTAMP Types
java编程中遇到的时区与时间问题总结
How to convert a String to a Date using SimpleDateFormat?
计算机时间、unix时间、linux时间、java时间为何以1970年1月1日为原点?从1970年1月1日开始计算?
协调世界时
格林威治时间
unix时间戳

cpu负载过高案例,解决方法记录

作者: 分类: 工作笔记 时间: 2015-08-20 评论: 暂无评论

先讲下问题来源,前一段时间线上服务器负载高的吓人,作为一台只部署了tomcat的应用服务器,平时load average 也就在零点几的水平,突然达到十几真是让人惊了个呆(⊙ˍ⊙),生怕服务器分分钟宕掉啊。

赶紧找问题发生原因吧,我以前没有遇到过这类问题,没啥经验可谈,首先服务器上top下,发现最耗cpu的就是我们的应用服务器,如图:

负载高

我马上想到的是我们有异步处理的多线程程序,是不是哪个程序死锁了或者一直跑着,不释放。于是查看了最近修改的代码,也没有发现那里可能会发生死循环,那干脆打印下堆栈看看到底都有什么线程在跑着吧。到jdk的bin路径下 用jstack pid >xxx.stack命令打印堆栈内容。
堆栈
快1MB的内容,真的没法去找,都是一些TIMED_WAITINGWAITING这些可以先不用去看,优先级高的应该是那些RUNNABLE的,因为都已经等待了,就消耗不到cpu了啊。看了半天也没看出啥名堂,最后在其他博客上取取经,发现了实用的技巧。

具体思路:

首先要找到哪几个线程在占用cpu,之后再通过线程的id值在堆栈文件中查找具体的线程,看看出来什么问题。

OK,实战开始,第一个命令

ps -mp pid -o THREAD,tid,time

其中pid 换成要查看的进程的id值,这个命令的作用就是打印出进程下有哪几个线程在跑,并且分别占用cpu多长时间。

ps不熟悉话,可以在linux下查看man ps文档,看看具体那几个命令的作用

-p的后面跟着要显示的进程的id号,
-m的意思是显示这个进程下的所有线程
-o的意思是格式化要输出的内容

通过上边这个命令,显示出一堆线程,通过筛选之后发现了几个占用cpu很长的线程

|%CPU | TID| TIME |
|-----|-----|----------|
|17.3 |16172|12:18:58 |
|79.9 |16275|2-08:49:22|
|19.8 |418 |12:16:02 |
|32.6 |13071|12:17:14 |

这下可算找到罪魁祸首了,看第二个线程cpu占用的吓人,并且占用时间也是高的离谱,我记住它了( *⊙~⊙)。

第二个命令,将找到的线程id号转成十六进制

printf "%x\n" tid

这个很简单,就不解释了,得到结果是3f93

之后就是在最初打印的堆栈信息中查找这个线程,Bingo!
bug_problem

请原谅,把关键信息注释上了,哈哈。找到这个线程,可以看到具体出问题的代码行了,对就是我注释那几个代码行,之后就在源码中查找下,发现真的有一个死循环在那里(⊙﹏⊙)b,好了既然找到了那就fix掉就可以了。

在一次上线之后服务器的情况:

fix_bug

又健健康康的啦~~~。

结尾再费点唾沫,查找好cpu的线程时一共有四个,其实那三个我也找了,发现是我们自己创建的线程池,所以不是问题啦。

参考文章:
线上应用故障排查之一:高CPU占用

悟空帮你理解https

作者: 分类: https 时间: 2015-08-05 评论: 暂无评论

[TOC]

开篇

相信程序猿们都有所了解https,就我个人而言一个月前我只知道https连接是安全的,加密了的,但是具体怎么加密的,证书是怎么认证的,为什么安全,我说不出来一二三。如果你跟我有一样的情况,那我们一起来探明它吧。

https连接过程

我们先来个简明概要的:
超简版


  1. 客户端(浏览器) -> 服务器(网站): hello服务器 , 加密算法a,b,c,hash算法d,e,f,ssl版本x.x
  2. 服务器(网站) -> 客户端(浏览器):选择a加密算法 和e hahs算法,我的证书
  3. 客户端 (浏览器)-> 服务器(网站):hash值hash1,消息message1,加密了的随机数secret1
  4. 服务器(网站) ->客户端(浏览器):hash值 hash2 消息message2
  5. 后续开始加密通信

看完之后,是不是啥也没懂,O(∩_∩)O哈哈~,不要急,接着看下更详细的,记得要跟上面的对比下:

详细版

  1. 客户端向服务器say hello

客户端(浏览器) -> 服务器(网站):hello服务器,我支持a,b,c 加密算法和 d,e,f hash算法,支持ssl版本是 x.x

  1. 服务器收到后,选择一组算法连带着证明自己的证书一并发送给客户端

服务器(网站) -> 客户端(浏览器):我选择 a 加密算法 和 e hash算法 还有我的证书(表明公钥,有效期,拥有者,签名者 bla,bla)

  1. 客户端验证证书的合法性,生成一个随机数 123,将要发送的消息(hello)使用e hash算法,算好hash值 hash1 ,使用加密算法a ,用随机数加密要发送的消息(hello)得消息message1,用证书中的公钥加密随机数得 secret1

客户端 (浏览器)-> 服务器(网站): hash值hash1,消息message1,加密了的随机数secret1

  1. 服务器用自己的私钥将secret1解密得随机数123,再使用随机数123通过加密算法a 解密消息message1得到hello,再通过算法e 算出消息hello的hash值 hash1,与传过来的hash值比对一致,正确。
    一致之后,服务器自己选择一条消息(hello,too),使用hash算法e 算出hash值 hash2,使用随机数123加密消息(hello,too)得消息message2

服务器(网站) ->客户端(浏览器):hash值 hash2 消息message2

  1. 验证成功

后续开始加密通信,使用的是随机数123和加密算法 a 来加密要通信的信息,并用hash算法 e 来计算要通信的信息

好了,大致通信就是这个过程,当然上述还有很多地方需要解释,只是想给大家一个宏观上的流程,之后我会一一列举解释的。

  • 保护世界的算法-RSA算法
  • 对称加密和hash算法
  • 伪随机数,猜不透的伪随机
  • 信任是根本-证书,CA认证

保护世界的算法-RSA算法

这个标题起的太霸气了,但对RSA来说真的是当之无愧。因为https的安全就是基于RSA算法不会被攻破的基础上的,如果以后RSA算法可以被攻破,那计算机的世界就没有安全可言了。算法原理我自己还没弄的十分懂,所以我就不班门弄斧了,我能理解的就是,RSA算法是一种非对称算法,什么又是非对称算法呢?就是加密和解密不用同一个密钥,分公钥和私钥,你用公钥加密只能用私钥才能解密,反之同理私钥加密只能用公钥解密。如果想了解原理,附上,阮一峰的网络日志-《RSA算法原理(一)》,话说解释的已经很好了,但是我还是没太懂o(╯□╰)o,数学真是捉急啊。

对称加密和hash算法

对称加密,相比于非对称加密就是,加密解密都需要同一个密钥,目前主流的有:DES、3DES、AES 等等。
hash算法,其实这个也好解释,像我们编程语言中都会有hash算法,最常用的就是hashcode方法,其实hash的本质就是摘取原信息的一部分来运算得到一个可以代表原信息的token串,当然hash算法都存在所谓的碰撞,hash的作用就是可以快速的来验证信息的相等或不等(虽然有一定的碰撞几率,但是在很多情况下可以忽略),比较有名的hash算法:MD4、MD5、SHA-1等等。

https中使用加密算法加密通信过程中信息,使用的密钥是浏览器和服务器都拥有的随机数,建立一次连接用一个随机数,而hash的用处就是来保证信息在这个过程中完整性以及不被替换。

伪随机数,猜不透的伪随机

java中的random相信大家都用过,那就是一种伪随机数。浏览器生成的伪随机数一定要达到近似的随机,否则被猜到每次生成的数那这https连接也就没有意义了。
这里的随机数,在握手之后就用来浏览器和服务器的通信的密钥了,所以重要性可想而知。

信任是根本-证书,CA认证

最后,就是CA证书啦,这个我要多费点唾沫O(∩_∩)O~~。
我们先回顾上述的https建立连接的过程,其中服务器端将一个证书发送给客户端,证书中包含公钥和所属的单位以及有效期。只有客户端信任了这个证书,我们才能进行下面的步骤。
好关键的来了,首先这个证书是谁颁发的,我们怎么判断证书为合法的呢?

证书是CA颁发的,也就是认证机构,他们去负责验证网站的合法性以及给网站生成证书。CA也就是这个过程中的第三方,说到底我们还是需要无条件的去信任这个第三方,如果没有这个信任也就无从来判断信息的合法性了。

验证合法性,浏览器中都会内置CA机构的根证书,如图:
chrome_certificate .png
这些都是浏览器信任的根证书,服务器发过来的证书都是以根证书为基础的后续枝叶。这个还要说一下,通过根来验证后续的证书合法性的具体做法,也就是利用了RSA非对称加密,CA在颁发证书的时候用自己的私钥加密一条信息放在颁发的证书里,只有对应CA的公钥才能解密。
我们来看看百度的https的证书
baidu https.png

那么问题又来了(好问的小孩),如果浏览器没有内置某个认证机构的根证书呢?

如图:
12306 https.png 12306 certifer.png
这就是咱们大名鼎鼎的12306,哈哈,让我笑五分钟,收。浏览器内置的证书都是对机构高度信任的,像咱这个人家都不认的,这也是为了我们安全,如果我们手动把证书安装到浏览器中,以后12306随便签名一个证书,我们都会......。留下想象空间。

关键点

个人认为整个https通信中,有几处至关重要的点,也是我们平时自己网站需要注意的。

  1. 私钥:简直是忒重要了,要是让别人那到这个,那不就是想干啥干啥了,要是我一定锁到小金库中。整个https握手的过程中,私钥只使用了一次,之后就是用随机数当做密钥通信了。这样就算让你获取到了密钥,也只局限这一次,并且保证了私钥没有在网络中传输,大大提高了安全性。

  2. 随机数的生成方式: 要是把浏览器的随机数生成方式,那任你什么算法,通通都没用了,所以随机数生成的方式是一个很大的课题啊。

  3. 证书,第三方,整个安全体系我们还是要取信任第三方,生活中也是这样,我们整个社会的经济也是建立在信用基础上的。但是我用程序猿的思维想象,如果我取得一个著名CA的控制权,我岂不是取得了互联网上大多数电脑的控制权?想想也挺爽的哈。

最后发言

本想把本文写的简单点,但是能力有限,又写成了一篇流水文了,抱歉抱歉。笔者花了大概三周的时间看了这方面的文章,感觉也学到了不少东西,就把自己所接受到的知识,转成直白点的话,叙述了一遍。文中如果有什么理解不对的地方,还望指出,帮助学习。

参考文章:

《数字证书原理》(推荐)
《How does HTTPS actually work?》
阮一峰的网络日志-《图解SSL/TLS协议》
阮一峰的网络日志-《SSL/TLS协议运行机制的概述》
阮一峰的网络日志-《RSA算法原理》
伯乐在线-《HTTPS是如何保证连接安全:每位Web开发者都应知道的》
伯乐在线-《HTTPS连接的前几毫秒发生了什么》
coodoing-《HTTPS工作原理和TCP握手机制》
果壳-《HTTPS那些事(一)HTTPS原理》
果壳-《HTTPS那些事(二)SSL证书》

java正则掉坑实录

作者: 分类: 杂谈 时间: 2015-08-03 评论: 暂无评论

大学时候对正则很感兴趣,还特意学习了perl的《精通正则表达式》,平时开发中也会经常用到,但是都写的比较简单的正则,也没有太考虑效率,额是主Java语言的。

最近正好写了个抓取代理ip地址的程序,用到了正则匹配,本来挺简单的一个问题,但是对正则的贪婪和非贪婪匹配没有理解深刻,导致在上面耗了很长时间。

先简单说下贪婪和非贪婪匹配:

String regexGreedy = "<.*>"
String regexNoGreedy = "<.*?>"

String content = "<tr><td>abc</td><tr>"

上面第一个regexGreedy正则匹配到的是content全部内容,而第二个regexNoGreedy匹配到的是<tr>内容。
显而易见,一种是要最多,另一种是要最少

好了,其实问题很简单,但是实战中就有可能出现问题,以下是我的实战内容:

需求:我想抓取某个公布代理ip地址的网站上数据。
程序:

public class Proxy {
    
    private final static ConcurrentHashMap<String, Integer> ipMap = new ConcurrentHashMap<String, Integer>();
    
    public static Logger logger = Logger.getLogger(Proxy.class);
    
    //地址就不放上来了。。。
    private static String  proxyUrl = "xxx";
    
    private static Header header_Encode = new Header("Accept-Encoding","Accept-Encoding:gzip,deflate,sdch");

    /**
     * 刷新代理ip
     */
    public static void flush(){
        
        org.apache.commons.httpclient.HttpClient httpClient = new org.apache.commons.httpclient.HttpClient();
        GetMethod getMethod = new GetMethod(proxyUrl);
        getMethod.setRequestHeader(header_Encode);
        InputStream urlStream =null;
        BufferedReader reader = null;
        try {
            httpClient.executeMethod(getMethod);
            if(getMethod.getStatusLine().getStatusCode()==HttpStatus.SC_OK){
                System.out.println(getMethod.getRequestHeader("Accept-Encoding").getValue());
                try{
                    urlStream = new GZIPInputStream(getMethod.getResponseBodyAsStream());
                }catch(IOException e){
                    urlStream =  new BufferedInputStream(getMethod.getResponseBodyAsStream());
                }
                  
                reader = new BufferedReader(new InputStreamReader(urlStream,"utf-8"));  
                StringBuilder content = new StringBuilder();  
                String line = null;
                while((line = reader.readLine()) != null) {  
                    content.append(line);
                }
                //System.out.println(content.toString());
                parser(content.toString());
                  
            }
        } catch (HttpException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            
            try {
                
                if(reader!=null){
                    reader.close();
                }
                if(urlStream!=null){
                    urlStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            
        }
        
    }
    
    
    private static HashMap<String,Integer> parser(String content) {
        HashMap <String ,Integer> map = new HashMap<String,Integer> ();
        
        String regexBody = "<tbody>(.*)</tbody>";
        String regexTr ="<tr><td>([^<]*)</td><td>([0-9]*)</td>.*</tr>";
        Pattern pattern = Pattern.compile(regexBody);
        Matcher matcher = pattern.matcher(content);
        
        if(matcher.find()){
            content = matcher.group(1);
        }
        
        content = content.replaceAll("[\\s*\\n*\\s*]", "");
        System.out.println(content);
        pattern = Pattern.compile(regexTr,Pattern.MULTILINE | Pattern.DOTALL);
        matcher = pattern.matcher(content);
        
        String ip = "";
        Integer port = null;
        while(matcher.find()){
            
            try{
                ip = matcher.group(1);
                port = Integer.parseInt(matcher.group(2));
                map.put(ip, port);
                System.out.println("ip="+ip+"|port="+port);
            }catch(Exception e){
                logger.error(e.getMessage(),e);
            }
        }
        
        return map;

    }
    
    public  static void main(String[] args){
        Proxy.flush();
    }

}

程序比较简单,看一下大致都懂,但是我当时测试的时候无论怎么抓取都是抓取到网页上最后一组数据。
最终定位的问题就在正则匹配上:
String regexTr ="<tr><td>([^<]*)</td><td>([0-9]*)</td>.*</tr>";
这个正则是贪婪匹配,所以是要最多

修改后
String regexTr ="<tr><td>([^<]*?)</td><td>([0-9]*?)</td>.*?</tr>";
为非贪婪匹配,就可以捕获到所有了。

问题很简单,贪婪和非贪婪概念我也都懂,但是要灵活的在实战中运用就需要多多使用正则,积累经验,争取下次不要再掉坑了。

Top ↑