window下解决端口占用问题

window下解决端口进程的命令:

  1. netstat -ano | findStr 8080 找到对应的线程pid,比如10025
  2. 使用taskKill /F /pid 10025杀死线程
阅读更多

什么是协程

协程(Coroutine)又称为微线程,我们知道线程是CPU的执行的最小单位,线程执行的最小代码单位是方法。

比如在执行的时候,一个线程从程序的入口调用Main方法,Main调用A方法,A方法又调用B方法,整个函数的执行完成的顺序是B->A->Main。这个调用的顺序是明确的,是通过压栈和出栈的方式确定的。

而协程不同, Main调用B,在调用B的过程中可以中断,Main函数继续执行一会,Main再中断,B继续再执行一会, 继续执行的代码是上次中断的地方。

用伪代码表示两个方法:

funcA(){
     funcB();
     print 4;
     print 5;
     print 6;
}
funcB(){
     print 1;
     print 2;
     print 3;
}

如果是用正常的单线程线程来执行的时候,打印结果是123456,如果采用协程,打印结果就有可能是142536.

协程的执行的结果有点和多线程类似,但本质与多线程不同,线程有上下文切换,存在变量的拷贝,而协程只是轻量级的方法中断,所以切换效率是高于线程。

协程所有的变量都是共享内存,访问不需要加锁,使用时只需简单的判断,不存在线程不安全问题。

在Java中,还不支持协程的机制,所以用C#来演示下协程的过程。

   static void Main(string[] args)
   {
        System.Console.WriteLine("执行方法:Main");
        IEnumerable<int> intList = Xc.GetList();
        foreach (int i in intList)
        {
             System.Console.WriteLine("协程1:执行");
             Console.WriteLine("协程1:获得返回的结果是:" + i);
        }
   }

   class Xc
   {
        public static IEnumerable<int> GetList()
        {
             System.Console.WriteLine("执行方法:GetList");
             for (int i = 0; i < 10; i++)
             {
                  yield return i;
                  System.Console.WriteLine("协程2: 执行");
                  System.Console.WriteLine("协程2:doSomething");
                  Thread.Sleep(1000);
             }
        }
   }

执行结果如下:

   执行方法:Main
   执行方法:GetList
   协程1:执行
   协程1:获得返回的结果是:0
   协程2: 执行
   协程2:doSomething
   协程1:执行
   协程1:获得返回的结果是:1
   协程2: 执行
   协程2:doSomething
   协程1:执行
   协程1:获得返回的结果是:2

从上面的结果可以看出,在协程1循环执行的时,Main方法会中断,执行GetList方法,执行GetList到达约定中断点,Main方法又继续执行。

C10K的问题

什么是C10K问题

随着互联网的普及,web的访问呈几何倍数的增长,我们知道一个请求和响应的过程的背后是连接的互换数据,最初的服务器都是基于进程/线程模型的,新到来一个TCP连接,就需要分配1个进程(或者线程)。

而进程和线程又是系统昂贵的资源,一台机器创建的线程数量和进程数量是有限的,不可能无限制的创建。

C10K的核心问题就是即使在硬件资源都满足的情况先,系统也难以承载有10000个客户端连接请求

造成C10K问题的原因

造成C10K问题的本质其实是操作系统的问题,对于传统的阻塞I/O处理方式,当线程或进程创建的足够多时,即使服务器硬件条件满足,也会导致系统的卡顿和崩溃。所以要解决C10K的问题,就应该尽可能的降低CPU的开销和进程的创建。

怎么解决C10K的问题

上面分析了C10K的本质,所以解决办法就围绕着,降低进程的开销,比如让一个进程能够管理多个连接。而如何是一个进程管理多个连接呢?因为在服务端无法知道到底是哪个端口会发来数据。我个人理解具体方法有下面几种:

  1. 同步轮询(select

    方法很简单,直接挨个检查处理各个连接,当所有连接都有数据的时候,方法没有问题,如果有一个连接没有数据,那整个流程就阻塞在哪里,端口就无法进行获得。如果一个进程出来的过多,也会带来性能问题。

  2. 智能跳过轮询 (poll

    这个方法在上面的方法又改进了一步,在读取前先判断当前句柄是否已经是ready状态,如果不是则跳过。

  3. 轮询标记有数据,然后再轮询(epoll

    既然逐个排查所有文件句柄状态效率不高,可以先标记哪些句柄有变化,然后再读取变化的数据。

深入volatile关键字

在Java多线程中,有一个特殊的关键字volatile,这个通常成为一个“轻量级锁”,下面我们就来深入的了解这个关键的作用和原理。

线程的内存备份

首先看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class VolatileThread extends Thread {
private boolean isRuning=true;
private void setRuning(boolean runing){
this.isRuning=runing;
}

public void run(){
System.out.println("进入Run方法");
while (isRuning){

}
System.out.println("线程结束");
}

public static void main(String[] args) throws InterruptedException {
VolatileThread volatileThread = new VolatileThread();
volatileThread.start();
Thread.sleep(3000);
volatileThread.setRuning(false);
System.out.println("runing设置成false,让线程停止");
Thread.sleep(1000);
System.out.println(volatileThread.isRuning);
}
}

在上面的代码并没有打印出“线程结束”的信息,因为我在主线程更改了isRuning 的值,并没有影响到线程中的数据。

产生这个的原因是因为JDK在创建线程的时候,都会从主内存中拷贝一份数据,所以线程的读取的变量的具有一定延迟

深入volatile关键字

使用volatile

对上面的代码进行修改,把isRuning变量使用volatile 关键字修饰,这样我们就能看到线程能够正常的停止了。下面我们总结下volatile的作用

1
2
3

如果变量被volatile关键字修饰, 则当变量改变的时候强制线程从主内存中读取和写入变量

CPU的缓存怎么办

代码最终的是由CPU执行的,为了保证CPU的执行效率,在读取数据的时候,CPU是优先把数据缓存到自己的高速缓存中,高速缓存带来了效率上面的提高,也同样带来了数据一致性的问题。

深入volatile关键字

例如下面这一段简单的代码:

1
2
3

count++;

当程序运行的时候,count会被拷贝到CPU高速缓存中,知道执行结束才会重新刷到主内存中。

如果在多线程的环境中,就会出现数据不一致的问题。

解决这个问题的方法有两种:

  1. 在总线的位置加锁,一次只允许一个CPU访问内存。

  2. 使用缓存一致性协议,当CPU发现当前的变量是volatile变量,就会被告知通知其他CPU告诉该变量的缓存无效,这样CPU就会从内存中重新加载数据

volatile 不具备原子性

共享变量只是在读和写的时候具有原子性,但是复杂的count++运算不具备原子性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class AppTest {
private volatile int count = 0;

public static void main(String[] args) throws InterruptedException {

AppTest app = new AppTest();

for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(() -> {
for (int m = 0; m < 1000; m++) {
app.count();
}
});
thread.start();
}
System.out.println(app.count);
for (int i=0;i<10;i++){
Thread.sleep(1000);
System.out.println(app.count);
}
}

private void count() {
count++;
}
}

打印结果:

1
2
3
4
9444777
9578523
9578523

看到最终结果不是10000000。

CPU是如何实现运算

CPU的构成

我们知道CPU是芯片的集合,主要成分是硅。CPU的最小构成单位是一个PN节点,也就是我们常说的二极管。下面我们就聊一聊什么是二极管

PN节点 (二极管)

PN节点是一个硅晶体进行掺杂,分别在两侧掺入硼和磷,这样的硅晶体具有单项导电性,这样就形成一个PN节点。具体如下图:

CPU运算

由于具有单项导电性的特点,我们就能根据收到的电压变化,来确定输出的结果,我们假设收到高电压是1 ,低电压是0,PN节点的具体表现:

CPU运算

实现基本运算

根据上面的分析,我们可以尝试实现一个与门的电路实现,首先我们要清楚与门的具体逻辑。

有两个输入参数,只有同时为1的时候,才输出1,具体表示如下:

输入输出 1 0
1 1 0
0 0 0

实现电路图如下:

CPU运算

从图上可以看出来,由于C端的高电压的作用和向导电性,无论A和B哪一个是低电压,输出端Z都会获得低电压。只有两边同时为高电压的时候,Z才会获得高电压

这样的话,CPU就可以根据电信号来进行与门的计算。

程序如何运行的

在写代码的时候,我们直接在没有编译报错的时候,直接点击运行后,ide会直接把程序的结果输出到控制台上,代码如下:

1
2
3
4
5
6
7
  public static void main(String[] args) { 
int i=17;
int j=5;
int sum=i+j;
System.out.println(sum);
}

这段代码最终的结果是在控制台上面打印出:22,但是这个结果到底是怎么被执行的呢?

CPU能做什么

在硬件的世界里面,只有0和1,就是这么简单的0和1,到底是怎么做加法的呢?

我们知道CPU的功能是执行指令,有三个简单的基本操作:与,非,或三种运算。在加上位的运算一种有5种:&,|,~,<<,>>. 利用这个几个运算如何实现代码中的15+5的运算?

首先,把加法拆解,分成两个部分: 把个位和个位相加,如果有进1的话,就用进1的值十位与另一个十位相加。得到的和在进行相加。

  1. 把15+5进行拆解就是 7+5=12,发现5+5有进位10;
  2. 利用进位的十位与10+10 =20
  3. 再把两个的和相加,20+2=22 ,没有再进位,运算结束。

根据上面的分析,我们可以使用递归的方法,写出加法的位运算代码如下:

1
2
3
4
5
6
7
static int add(int i, int j){
if(j == 0)
return i;
int sum = i ^ j;//得到个位相加
int carry = (i & j) << 1;//得到进位相加
return add(sum, carry);
}

对这个算法进行封装成一个CPU指令,我们就可以利用二进制进行进行运算。

Java代码最终的编译结果

我们知道Java的代码最终是经过编译器,转换成字节码最终由JVM解释执行,具体过程如下:

Java过程

当Java代码最终转换成字节码的时候,JVM虚拟机执行对应的字节指令,最终传递给CPU来执行代码,CPU计算的过程我们已经分析过,最终会调用位运算来实现加法。

Tomcat不安全字符的处理

做项目的时候碰到一个问题,就是Tomcat在处理含有|,{,}的字符的Url时候,发现请求没有到达指定的Controller上面,而在Access_log中写入了get null null 400的错误信息,从网上也翻了几个资料最终确定是tomcat的一个问题(个人觉得也是一个缺陷)

问题的由来

Tomcat根据rfc的规范Url中不能有类似|,{,}等不安全字符串,但在实际的操作中有时为了数据完整性和加密的方式都需要有|,{,}出现,这样的话Tomcat会直接告诉客户端Bad Request.

对于这个问题,很多人也提出很多不同的看法:https://bz.apache.org/bugzilla/show_bug.cgi?id=60594,经过修改,最终Tomcat把权限开放出来,通过tomcat.util.http.parser.HttpParser. requestTargetAllow这个配置选项,允许不安全字符的出现。Tomcat详细配置

解决方法

经过几次探索,有以下几个方法能够解决这个问题:

  1. 把请求的Url进行编码,这个对源头进行处理,来规避这个问题,如果是第三方来调用的url就无能无力。

  2. 修改Tomcat的配置文件(Tomcat\conf\catalina.properties),适用tomcat 7以上的版本

    1
    2
    3

    tomcat.util.http.parser.HttpParser.requestTargetAllow=|{}

  3. 使用其它服务器进行中转,比如IIS和Apache

详解.net中IL语言

什么是IL语言

中间语言,又称(IL语言)。充当Clr与.net 平台的中间语言,比如用C#编写程序,编译器首先是把C#代码转译成IL语言,最终由Clr解释执行,下面我们学习下IL语言。

如何读懂IL语言

  • 写一个helloworld的.net 程序,编译运行完成。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    		static void Main(string[] args)
    {
    Console.WriteLine("hello world");
    }
    ```

    - 使用ildasm.exe(C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6 Tools)反编译代码,得到IL代码如下:

    ```IL
    .method private hidebysig static void Main(string[] args) cil managed
    {
    .entrypoint
    // 代码大小 13 (0xd)
    .maxstack 8
    IL_0000: nop
    IL_0001: ldstr "hello world"
    IL_0006: call void [mscorlib]System.Console::WriteLine(string)
    IL_000b: nop
    IL_000c: ret
    } // end of method Program::Main
    ```

    - 查找对应的[指令表](http://blog.csdn.net/xiaouncle/article/details/71248830),来确定对应的含义


    <table class="table">
    <thead>
    <tr>
    <th>指令名称</th>
    <th>说明</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td>Ldstr</td>
    <td>推送对元数据中存储的字符串的新对象引用。</td>
    </tr>
    <tr>
    <td>Nop</td>
    <td>如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作。</td>
    </tr>
    <tr>
    <td>Call</td>
    <td>调用由传递的方法说明符指示的方法。</td>
    </tr>
    <tr>
    <td>Ret</td>
    <td>从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。</td>
    </tr>
    </tbody>
    </table>

    - 其它几个名词的的解释

    **hidebysig**: 与之对就的是hidebyname,这个是确定使用方法的签名还是使用方法的名称来确定调用哪个方法.


    - 整个的IL语言解释

    ```IL
    .method private hidebysig static void Main(string[] args) cil managed
    {
    .entrypoint //代码入口
    // 代码大小 13 (0xd)
    .maxstack 8 //整个程序的堆栈大小
    IL_0000: nop //无实在意义
    IL_0001: ldstr "hello world" //定义字符
    IL_0006: call void [mscorlib]System.Console::WriteLine(string) //调用WriteLine变量
    IL_000b: nop
    IL_000c: ret //返回
    } // end of method Program::Main
    ```


    ### 更复杂的Demo

    - 添加编写如下C#代码:

    ``` C#
    class Program
    {
    static void Main(string[] args)
    {
    var a = 0;
    var b = 1;
    var c = Add(a, b);
    Console.WriteLine(c.ToString());
    }

    public static int Add(int x,int y)
    {
    return x + y;
    }
    }

    ```

    - 生成相关的IL代码及解释

    ``` IL
    .method private hidebysig static void Main(string[] args) cil managed
    {
    .entrypoint
    // 代码大小 27 (0x1b)
    .maxstack 2
    .locals init ([0] int32 a,
    [1] int32 b,
    [2] int32 c) //定义3个变量
    IL_0000: nop
    IL_0001: ldc.i4.0 //将整数值 0 作为 int32 推送到计算堆栈上。
    IL_0002: stloc.0 //从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中。
    IL_0003: ldc.i4.1 //将整数值 1 作为 int32 推送到计算堆栈上。
    IL_0004: stloc.1 //从计算堆栈的顶部弹出当前值并将其存储到索引 1 处的局部变量列表中。
    IL_0005: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上,这里指a。
    IL_0006: ldloc.1 //将索引 1 处的局部变量加载到计算堆栈上,这里指b。
    IL_0007: call int32 ILTest.Program::Add(int32,
    int32) //调用Add方法
    IL_000c: stloc.2 //将索引 2 处的局部变量加载到计算堆栈上,这里指c。
    IL_000d: ldloca.s c //将位于特定索引处的局部变量的地址加载到计算堆栈上(短格式)。
    IL_000f: call instance string [mscorlib]System.Int32::ToString()
    IL_0014: call void [mscorlib]System.Console::WriteLine(string)
    IL_0019: nop
    IL_001a: ret
    } // end of method Program::Main

    ```

    Add方法:

    ``` IL
    .method public hidebysig static int32 Add(int32 x,int32 y) cil managed
    {
    // 代码大小 9 (0x9)
    .maxstack 2
    .locals init ([0] int32 V_0) //创建一个V_0的局部变量
    IL_0000: nop
    IL_0001: ldarg.0 //将索引为 0 的参数加载到计算堆栈上。
    IL_0002: ldarg.1 //将索引为 1 的参数加载到计算堆栈上。
    IL_0003: add //将两个值相加并将结果推送到计算堆栈上。
    IL_0004: stloc.0
    IL_0005: br.s IL_0007 //无条件地将控制转移到目标指令(短格式)
    IL_0007: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上。
    IL_0008: ret
    } // end of method Program::Add

服务器CPU居高不下--解决问题历程

基本的概述

在一个服务器的集群上面,服务器的CPU长时间居高不下,响应的时间也一直很慢,即使扩容了服务器CPU的下降效果也不是很明显。

对于CPU过高的原因,可以总结到以下原因:

  • 太多的循环或者死循环

  • 加载了过多的数据,导致产生了很多的大对象

  • 产生了过多的对象,GC回收过于频繁(如:字符串拼接)

对于上面的情况,难点不是优化代码,难点在于定位到问题的所在,下面我们就用Dump抓包的方式来定位到问题的所在。介绍这个内容之前,我们要先回顾下.Net中垃圾回收的基础知识和一个工具的准备。


基础知识


垃圾回收触发条件

  • 代码显示调用System.GC的静态方法

  • windows报告低内存情况

  • CLR正在卸载AppDoamin

  • CLR正在关闭

大对象垃圾回收

CLR将对象分为大对象和小对象,认为大于85000字节或者更大的字节是大对象,CLR用不同的方式来对待大对象和小对象:

  • 大对象不是在小对象的地址空间分配,而是在进程地址空间和其他地方分配

  • GC不会压缩大对象,在内存中移动他们的代价过高,但这样会造成地址空间的碎片化,以至于会抛出OutOfMemeryException 异常。

  • 大对象总是在第二代回收。

工具准备

  1. 下载[windbg文件](dbg_amd64_6.12.2.633.msi)

  2. 相关DLL准备clr.dll和sos.dll,(都在对应.Net版本安装目录下面,我的安装目录在C:\Windows\Microsoft.NET\Framework64\v4.0.30319)

  3. 一个cpu运行的较高的时期的DUMP文件(下面会说如何获取)

  4. 准备测试代码,此处为了演示方便,简单了写了一个有潜在问题的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public  class Common
{
public static List<string> GetList()
{
var list=new List<string>();
for (int i = 0; i < 10000; i++)
{
list.Add(i.ToString());
}
return list;
}


public static string GetString(List<string> list)
{
var str = "";
foreach (var l in list)
{
str += string.Format("'{0}',", l);
}
if (str.Length > 0)
{
str.Remove(str.Length - 1);
}
return str;
}
}

我们知道在字符串的拼接的时候,每一个字符串都是一个对象,拼接后又产生了一个新对象,所以在GetString这个方法中会有大量的GC操作,下面我们就调用下这个代码,看下CPU的情况,为了模拟并发情况,我们开多个标签,每个标签每1s秒中刷新一次。

CPU

抓取Dump

在任务管理器中选择应用程序池对应的w3wp.exe,右击–>创建转储文件。创建完成后,会提示出指定的路径

taskManger

taskManger

根据上面的步骤,我们准备我们分析的文件如下:

dumpfile

分析Dump

  • 打开windbg,加载对应的dump文件

    dumpOpen
    dumpOpen

  • 配置Sysmbol,添加”cachec:\mysymbol;srvhttp://msdl.microsoft.com/download/symbols
    dumpOpen
    dumpOpen

  • load sos.dll和clr.dll,命令如下:

    .load D:\windbg\sos.dll 
    .load D:\windbg\clr.dll
    
  • 运行命令!threadpool 显示有关托管线程池的信息,其它一些SOS 调试扩展命令.

    dumpOpen

  • 运行!runaway 查询cpu占用时长比较长的几个线程Id

    dumpOpen

  • 运行~22s (进入线程查看),kb(查看对应的调用)

    dumpOpen

  • 运行~* kb 查看所有线程的堆栈调用

    dumpOpen

  • 在上面搜索GC和大对象出现的线程 (ctrl+f搜索:GarbageCollectGeneration和allocate_large_object )

    dumpOpen

  • 可以看到定位触发GC的线程是31号线程

  • 运行命令~31s 进入31线程,再运行!clrstack查看堆栈调用,最终可以定位到出问题的代码,是由于字符串的拼接导致大量的对象产生,从而触发了GC。

    dumpOpen

如何使用正则表达式

说到正则,可能很多人会很头疼这个东西,除了计算机好像很难快速的读懂这个东西,更不用说如果使用了。下面我们由浅入深来探索下正则表达式:

ps:此文适用于还有没有入门正则表达基础的读者

正则表达式可以简的定义成为一种字符串的匹配方式,至于来源可以参考:正则表达式{:target=_blank}

简单的使用

有这么一段字符串ABC12345ABC1234AB12C,对于这个字符串,如果想提取其中的字母,应该怎么办呢?

1
2
3
4
5
6
1. 可以找出所有的字母列表组成一个数组,[A,B,C...Z]     

2. 把字符串转成字符的数组,进行遍历

3. 如果是字母则继续,如果不是则直接继续下一个匹配

以上的分析过程则大概的讲述了不用正则表达式的过程,如果使用正则,怎么去写呢?

首先,我们是要匹配字母,那我要知道正则中用什么来表式字母呢?

1
2
3
4
5
6
7
8

[a-z] //匹配所有的小写字母
[A-Z] //匹配所有的大写字母
[a-zA-Z] //匹配所有的字母
[0-9] //匹配所有的数字
[0-9\.\-] //匹配所有的数字,句号和减号
[ \f\r\t\n] //匹配所有的白字符

根据上面的内容,可以看到用[A-Z]来表示A-Z的字母,当我们使用表达式[A-Z]去做测试的时候,发现可以匹配到所有的字母

正则表达式


基于对上面简单的描述,我们再继续深入,如果我想匹配里面所有的字符串呢?这就用到我们的其它的表达式了,把这种能够表达一个上下文的表达式称为元字符

下面我们看下我们需要的元字符:

字符 描述
{n}

n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。

{n,}

n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。

{n,m}

m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。

+

匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。

?

匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 。? 等价于 {0,1}。

.

匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用像"(.|\n)"的模式。

基于对上面的表格,可以找到我们想要的元字符{n,}和+,所以我们的表达式可以写成**[A-Z]{1,}** 或 [A-Z]+

正则表达式

到时目前为止我们已经可以写出一个简单的正则表达式了,上面的匹配结果为:

1
2
3
4
ABC
ABC
AB
C

在这个结果中,我们可以看出ABC和AB都是满足当前的正则表达式的,但为什么第一个不返回AB或者A呢?也就是下面的结果也满足当前的表达式:

1
2
3
4
5
AB
C
ABC
AB
C

贪婪模式

正则表达式一般趋向于最大长度匹配,也就是所谓的贪婪匹配。

上面的意思就是如果有多个可能都能匹配,则我尽可能的匹配更多的字符串。

实战–去除html中的html标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <ul class="dropdown-menu">
<li><a href="#" class="dropdown-header">业务功能</a>
</li>
<li><a href="#">信息建立</a>
</li>
<li><a href="#">信息查询</a>
</li>
<li><a href="#">信息管理</a>
</li>
<li role="separator" class="divider"></li>
<li><a href="#" class="dropdown-header">系统功能</a>
</li>
<li><a href="#">设置</a>
</li>
</ul>

对于上面的html标签,我们去除html标签,第一步是能够匹配到对应的标签,我们知道html标签是以“<”开始,并以“>”结束。

  1. 确定“<[**一堆任意的正则]**>”的正则形势

  2. 在“**一堆任意的正则”**正则中,可以是任意的字符,比如

    1
    2
    3

    <a href="blog.laofu.online">付威的网络博客</a>

  3. 根据2的分析,我们找到正则中可以代表所有字符的元字符“.”,则字符可以表示成<.+>

  4. 对于<.+> 这个正则来说,可以找到对应的html标签,但我们同样遇到了意想不到的结果,因为正则贪婪的模式的原因,匹配的结果也包含了我们想要的汉字

  5. 对于以上的问题,我们可以考虑把 “一堆任意的正则” 换成 “一堆不包含Html标签的正则”

  6. 针对上面的分析 我们可以把正则改成 <[^<>]+>

最终的匹配结果:

正则表达式

相关资料


表达式全集

字符 描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。串行“\\”匹配“\”而“\(”则匹配“(”。
^ 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
* 匹配前面的子表达式零次或多次。例如,zo*能匹配“z”以及“zoo”。*等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
? 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”。?等价于{0,1}。
{n} n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
{n,} n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
{n,m} mn均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
. 匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“(.|\n)”的模式。
(pattern) 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\(”或“\)”。
(?:pattern) 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
(?=pattern) 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
(?<=pattern) 反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。
(?<!pattern) 反向否定预查,与正向否定预查类拟,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。
x|y 匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。
[xyz] 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d 匹配一个数字字符。等价于[0-9]。
\D 匹配一个非数字字符。等价于[^0-9]。
\f 匹配一个换页符。等价于\x0c和\cL。
\n 匹配一个换行符。等价于\x0a和\cJ。
\r 匹配一个回车符。等价于\x0d和\cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于\x09和\cI。
\v 匹配一个垂直制表符。等价于\x0b和\cK。
\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\xn 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。.
\num 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
\nm 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若nm均为八进制数字(0-7),则\nm将匹配八进制转义值nm
\nml 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\un 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。

常用正则表达式

用户名 /^[a-z0-9_-]{3,16}$/
密码 /^[a-z0-9_-]{6,18}$/
十六进制值 /^#?([a-f0-9]{6}|[a-f0-9]{3})$/
电子邮箱 /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/
/^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/
URL /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
IP 地址 /((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
HTML 标签 /^<([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)$/
删除代码\\注释 (?<!http:|\S)//.*$
Unicode编码中的汉字范围 /^[\u2E80-\u9FFF]+$/

参考资料:

  1. 正则表达式文档

  2. 正则表达式 - 教程

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×