java之Matcher类详解
在JDK 1.4中,Java增加了对正则表达式的支持。
java与正则相关的工具主要在java.util.regex包中;此包中主要有两个类:Pattern、Matcher。
Matcher
声明:public final class Matcher extends Objectimplements MatchResult
Matcher 类有final 修饰,可知他不能被子类继承。
含义:匹配器类,通过解释 Pattern 对 character sequence 执行匹配操作的引擎。
注意:此类的实例用于多个并发线程是不安全的。
字段:
- /**
- * The Pattern object that created this Matcher.创建此对象的模式匹配。
- */
- Pattern parentPattern;
- /**
- * The storage used by groups. They may contain invalid values if
- * a group was skipped during the matching.组使用的存储。如果在匹配过程中跳过一个组,它们可能包含无效的值。
- */
- int[] groups;
- /**
- * The range within the sequence that is to be matched. Anchors
- * will match at these "hard" boundaries. Changing the region
- * changes these values.要匹配的序列中的范围。
- */
- int from, to;
- /**
- * Lookbehind uses this value to ensure that the subexpression
- * match ends at the point where the lookbehind was encountered.
- */
- int lookbehindTo;
- /**
- * The original string being matched.匹配的目的字符串。
- */
- CharSequence text;
- /**
- * Matcher state used by the last node. NOANCHOR is used when a
- * match does not have to consume all of the input. ENDANCHOR is
- * the mode used for matching all the input. NOANCHOR表示不必匹配所有的输入;ENDANCHOR表示必须匹配所有的输入。
- */
- static final int ENDANCHOR = 1;
- static final int NOANCHOR = 0;
- int acceptMode = NOANCHOR;
- /**
- * The range of string that last matched the pattern. If the last
- * match failed then first is -1; last initially holds 0 then it
- * holds the index of the end of the last match (which is where the
- * next search starts).最后一个匹配模式的字符串的范围。
- */
- int first = -1, last = 0;
- /**
- * The end index of what matched in the last match operation.在最后一次匹配操作中匹配的结束索引。
- */
- int oldLast = -1;
- /**
- * The index of the last position appended in a substitution.追加在替换中的最后位置的索引。
- */
- int lastAppendPosition = 0;
- /**
- * Storage used by nodes to tell what repetition they are on in
- * a pattern, and where groups begin. The nodes themselves are stateless,
- * so they rely on this field to hold state during a match.
- */
- int[] locals;
- /**
- * Boolean indicating whether or not more input could change
- * the results of the last match.
- *
- * If hitEnd is true, and a match was found, then more input
- * might cause a different match to be found.
- * If hitEnd is true and a match was not found, then more
- * input could cause a match to be found.
- * If hitEnd is false and a match was found, then more input
- * will not change the match.
- * If hitEnd is false and a match was not found, then more
- * input will not cause a match to be found.
- */
- boolean hitEnd;
- /**
- * Boolean indicating whether or not more input could change
- * a positive match into a negative one.
- *
- * If requireEnd is true, and a match was found, then more
- * input could cause the match to be lost.
- * If requireEnd is false and a match was found, then more
- * input might change the match but the match won't be lost.
- * If a match was not found, then requireEnd has no meaning.
- */
- boolean requireEnd;
- /**
- * If transparentBounds is true then the boundaries of this
- * matcher's region are transparent to lookahead, lookbehind,
- * and boundary matching constructs that try to see beyond them.
- */
- boolean transparentBounds = false;
- /**
- * If anchoringBounds is true then the boundaries of this
- * matcher's region match anchors such as ^ and $.
- */
- boolean anchoringBounds = true;
构造器
- Matcher() {
- }
- Matcher(Pattern parent, CharSequence text) {
- this.parentPattern = parent;
- this.text = text;
- // Allocate state storage
- int parentGroupCount = Math.max(parent.capturingGroupCount, 10);
- groups = new int[parentGroupCount * 2];
- locals = new int[parent.localCount];
- // Put fields into initial states
- reset();
- }
构造器有包访问权限,可知不能在包外通过new创建Matcher对象。
如何在自定义的包中得到Matcher类的实例?
Matcher类中没有合适的方法,查阅Pattern类有:
- public Matcher matcher(CharSequence input) {
- if (!compiled) {
- synchronized(this) {
- if (!compiled)
- compile();
- }
- }
- Matcher m = new Matcher(this, input);
- return m;
- }
可知需要通过Pattern对象调用matcher方法来返回Matcher 类的实例。
对照Matcher构造器源码,可知构造器将Pattern对象的引用赋于Matcher中变量parentPattern,目标字符串赋于变量text;并创建了数组groups和locals 。
数组groups是组使用的存储。存储的是当前匹配的各捕获组的first和last信息。
groups[0]存储的是组零的first,groups[1]存储的是组零的last,groups[2]存储的是组1的first,groups[3]存储的是组1的last,依次类推。关于捕获组的信息请看java之Pattern类详解中的组和捕获。
初始化后状态表:(具体分析见以下reset()方法)
变量 | 类型 | 值 |
first | int | -1 |
last | int | 0 |
oldLast | int | -1 |
lastAppendPosition | int | 0 |
from | int | 0 |
to | int |
|
groups |
|
|
locals |
|
|
parentPattern |
|
构造器传入的Pattern对象 |
text |
|
构造器传入的目标字符串 |
部分方法:
1、public String toString()
返回匹配器的字符串表示形式。包含可用于调试的信息的 Matcher
字符串表示形式。未指定确切格式。
源码:
- public String toString() {
- StringBuffer sb = new StringBuffer();
- sb.append("java.util.regex.Matcher");
- sb.append("[pattern=" + pattern());
- sb.append(" region=");
- sb.append(regionStart() + "," + regionEnd());
- sb.append(" lastmatch=");
- if ((first >= 0) && (group() != null)) {
- sb.append(group());
- }
- sb.append("]");
- return sb.toString();
- }
测试:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- System.out.println(m);
打印:
- java.util.regex.Matcher[pattern=(\w+)%(\d+) region=0,11 lastmatch=]
2、public Matcher reset()
重置匹配器。
- public Matcher reset() {
- first = -1;
- last = 0;
- oldLast = -1;
- for(int i=0; i<groups.length; i++)
- groups[i] = -1;
- for(int i=0; i<locals.length; i++)
- locals[i] = -1;
- lastAppendPosition = 0;
- from = 0;
- to = getTextLength();
- return this;
- }
- int getTextLength() {
- return text.length();
- }
可知reset()方法改变了变量first 、last 、oldLast、lastAppendPosition、from、to的值并将数组groups、locals初始化。
状态变化:
变量 | 类型 | 新值 |
first | int | -1 |
last | int | 0 |
oldLast | int | -1 |
lastAppendPosition | int | 0 |
from | int | 0 |
to | int |
|
groups |
|
|
locals |
|
|
测试1:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- if (m.find()) {
- System.out.println("开始索引:" + m.start());// 开始索引:0
- System.out.println("group():" + m.group());// group():ab%12
- }
- if (m.find()) {
- System.out.println("开始索引:" + m.start());// 开始索引:6
- System.out.println("group():" + m.group());// group():cd%34
- }
测试2:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- if (m.find()) {
- System.out.println("开始索引:" + m.start());// 开始索引:0
- System.out.println("group():" + m.group());// group():ab%12
- }
- m.reset();
- if (m.find()) {
- System.out.println("开始索引:" + m.start());// 开始索引:0
- System.out.println("group():" + m.group());// group():ab%12
- }
由测试1和测试2可知reset方法可将Matcher 对象状态初始化。
3、public Matcher reset(CharSequence input)
重置此具有新输入序列的匹配器。
- public Matcher reset(CharSequence input) {
- text = input;
- return reset();
- }
可知此方法在reset()方法的基础上改变了目标字符串的值。
测试:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- m.reset("ef%56-gh%78");
- while (m.find()) {
- System.out.println("group():" + m.group());
- }
打印:
- group():ef%56
- group():gh%78
4、public Pattern pattern()
返回由此匹配器解释的模式。
源码:
- public Pattern pattern() {
- return parentPattern;
- }
pattern()返回parentPattern,即构造器传入的Pattern对象。
5、public int groupCount()
返回此匹配器模式中的捕获组数。根据惯例,零组表示整个模式。它不包括在此计数中。
测试:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- System.out.println(m.groupCount());//
6、public String group()
返回当前查找而获得的与组匹配的所有子串内容。
查看group()源码:
- public String group() {
- return group(0);
- }
可知group()实际调用了group(int group)方法,参数group为0。组零表示整个模式。
7、public String group(int group)
返回当前查找而获得的与组匹配的所有子串内容。
8、public int start()
返回当前匹配的子串的第一个字符在目标字符串中的索引位置 。
源码:
- public int start() {
- if (first < 0)
- throw new IllegalStateException("No match available");
- return first;
- }
可知start()方法返回的是匹配器的状态first。
9、public int start(int group)
返回当前匹配的指定组中的子串的第一个字符在目标字符串中的索引位置 。
10、public int end()
返回当前匹配的子串的最后一个字符的下一个位置在目标字符串中的索引位置 。
源码:
- public int end() {
- if (first < 0)
- throw new IllegalStateException("No match available");
- return last;
- }
可知end()方法返回的是匹配器的状态last。
11、public int end(int group)
返回当前匹配的的指定组中的子串的最后一个字符的下一个位置在目标字符串中的索引位置 。
12、public boolean find()
在目标字符串里查找下一个匹配子串。如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。
源码:
- public boolean find() {
- int nextSearchIndex = last;
- if (nextSearchIndex == first)
- nextSearchIndex++;
- // If next search starts before region, start it at region
- if (nextSearchIndex < from)
- nextSearchIndex = from;
- // If next search starts beyond region then it fails
- if (nextSearchIndex > to) {
- for (int i = 0; i < groups.length; i++)
- groups[i] = -1;
- return false;
- }
- return search(nextSearchIndex);
- }
从源码中可知nextSearchIndex为下次查找匹配的开始位置;nextSearchIndex的值有三次判定:
1、last==first时,nextSearchIndex++;
2、nextSearchIndex<from时,nextSearchIndex=from;
3、nextSearchIndex>to时,return false;
可通过region(int start,int end)方法修改from和to,以此来影响下次查找匹配的开始位置。
注意:此方法会改变匹配器的状态:first、last和oldLast。
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- while (m.find()) {
- System.out.println("group():" + m.group());
- System.out.println("start():" + m.start());
- System.out.println("end():" + m.end());
- System.out.println("group(1):" + m.group(1));
- System.out.println("start(1):" + m.start(1));
- System.out.println("end(1):" + m.end(1));
- System.out.println("group(2):" + m.group(2));
- System.out.println("start(2):" + m.start(2));
- System.out.println("end(2):" + m.end(2));
- System.out.println();
- }
打印:
- group():ab%12
- start():0
- end():5
- group(1):ab
- start(1):0
- end(1):2
- group(2):12
- start(2):3
- end(2):5
- group():cd%34
- start():6
- end():11
- group(1):cd
- start(1):6
- end(1):8
- group(2):34
- start(2):9
- end(2):11
可知find()方法匹配了两个子串:ab%12和cd%34;每个子串有2组。
13、public boolean find(int start)
重置此匹配器,然后尝试查找匹配该模式,从指定的位置开始查找下一个匹配的子串。如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。
注意:此方法会改变匹配器的转态。
源码:
- public boolean find(int start) {
- int limit = getTextLength();
- if ((start < 0) || (start > limit))
- throw new IndexOutOfBoundsException("Illegal start index");
- reset();
- return search(start);
- }
从源码可知此方法首先重置匹配器,然后搜索匹配,下次查找匹配的开始位置为指定的start参数。
测试:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- if (m.find(1)) {
- System.out.println("开始索引:" + m.start());// 开始索引:1
- System.out.println("group():" + m.group());// group():b%12
- }
- if (m.find(0)) {
- System.out.println("开始索引:" + m.start());// 开始索引:0
- System.out.println("group():" + m.group());// group():ab%12
- }
- if (m.find()) {
- System.out.println("开始索引:" + m.start());// 开始索引:6
- System.out.println("group():" + m.group());// group():cd%34
- }
当有m.find(1)时,重置匹配器,从索引1处开始匹配,匹配的子串为“b%12”;
当有m.find(0)时,重置匹配器,从索引0处开始匹配,匹配的子串为“ab%12”;
当有m.find()时,并没有重置匹配器,从索引6处开始匹配,匹配的子串为“cd%34”;
14、public int regionStart()
报告此匹配器区域的开始索引。
源码:
- public int regionStart() {
- return from;
- }
可知end()方法返回的是匹配器的状态from。
15、public int regionEnd()
报告此匹配器区域的结束索引(不包括)。
源码:
- public int regionEnd() {
- return to;
- }
可知end()方法返回的是匹配器的状态to。
16、public Matcher region(int start,int end)
设置此匹配器的区域限制。重置匹配器,然后设置区域,使其从 start
参数指定的索引开始,到 end
参数指定的索引结束(不包括end索引处的字符)。
- public Matcher region(int start, int end) {
- if ((start < 0) || (start > getTextLength()))
- throw new IndexOutOfBoundsException("start");
- if ((end < 0) || (end > getTextLength()))
- throw new IndexOutOfBoundsException("end");
- if (start > end)
- throw new IndexOutOfBoundsException("start > end");
- reset();
- from = start;
- to = end;
- return this;
- }
从源代码中可知region方法首先调用reset()重置,然后对from 和to赋值,来设置匹配的目的字符串的范围。
测试:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- m.region(0, 4);
- while (m.find()) {
- System.out.println("group():" + m.group());
- System.out.println("regionStart():" + m.regionStart());
- System.out.println("regionEnd():" + m.regionEnd());
- }
打印:
- group():ab%1
- regionStart():0
- regionEnd():4
17、public boolean lookingAt()
从目标字符串开始位置进行匹配。只有在有匹配且匹配的某一子串中包含目标字符串第一个字符的情况下才会返回true。
源码:
- public boolean lookingAt() {
- return match(from, NOANCHOR);
- }
从源码中可知下次查找匹配的开始位置为from,可通过region(int start,int end)方法修改from的值。
测试:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- System.out.println(m.lookingAt());// true
- m = p.matcher("%ab%12-cd%34");
- System.out.println(m.lookingAt());// false
18、public boolean matches()
只有完全匹配时才会返回true。
源码:
- public boolean matches() {
- return match(from, ENDANCHOR);
- }
对比上一个方法lookingAt(),从代码上看差别很小,调用的match方法只有一个参数不一样;lookingAt中使用的NOANCHOR,而matches中使用的ENDANCHOR。
NOANCHOR表示不必匹配所有的输入;ENDANCHOR表示必须匹配所有的输入。
测试:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("%ab%12");
- System.out.println(m.matches());// false
- m = p.matcher("ab%12%");
- System.out.println(m.matches());// false
- m = p.matcher("ab%12");
- System.out.println(m.matches());// true
19、public Matcher appendReplacement(StringBuffer sb, String replacement)
将当前匹配子串替换为指定字符串,并将从上次匹配结束后到本次匹配结束后之间的字符串添加到一个StringBuffer对象中,最后返回其字符串表示形式。
注意:对于最后一次匹配,其后的字符串并没有添加入StringBuffer对象中,若需要这部分的内容需要使用appendTail方法。
20、public StringBuffer appendTail(StringBuffer sb)
将最后一次匹配工作后剩余的字符串添加到一个StringBuffer对象里。
源码:
- public StringBuffer appendTail(StringBuffer sb) {
- sb.append(getSubSequence(lastAppendPosition, getTextLength()).toString());
- return sb;
- }
查看源码有getSubSequence(lastAppendPosition, getTextLength()),即获取从lastAppendPosition索引处开始,到目的字符串结束索引处之间的子串。
lastAppendPosition为何值?
查阅Matcher类代码后,发现appendReplacement方法中有:
- lastAppendPosition = last;
last即目前最后一次匹配结束后的索引。
测试:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("前ab%12中cd%34后");
- StringBuffer s = new StringBuffer();
- while (m.find()) {
- m.appendReplacement(s, "app");
- }
- System.out.println(s);// 前app中app
- m.appendTail(s);
- System.out.println(s);// 前app中app后
21、public StringreplaceAll(String replacement)
将匹配的子串用指定的字符串替换。
- public String replaceAll(String replacement) {
- reset();
- boolean result = find();
- if (result) {
- StringBuffer sb = new StringBuffer();
- do {
- appendReplacement(sb, replacement);
- result = find();
- } while (result);
- appendTail(sb);
- return sb.toString();
- }
- return text.toString();
- }
查看源码可知此方法首先重置匹配器,然后判断是否有匹配,若有,则创建StringBuffer 对象,然后循环调用appendReplacement方法进行替换,最后调用 appendTail方法并返回StringBuffer 对象的字符串形式。
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- StringBuffer s = new StringBuffer();
- System.out.println(m.replaceAll("app"));// app-app
22、public String replaceFirst(String replacement)
将匹配的第一个子串用指定的字符串替换。
- public String replaceFirst(String replacement) {
- if (replacement == null)
- throw new NullPointerException("replacement");
- StringBuffer sb = new StringBuffer();
- reset();
- if (find())
- appendReplacement(sb, replacement);
- appendTail(sb);
- return sb.toString();
- }
查看源码可知此方法其实是 replaceAll方法的减配版本,只对第一次匹配做了替换。
测试:
- Pattern p = Pattern.compile("(\\w+)%(\\d+)");
- Matcher m = p.matcher("ab%12-cd%34");
- StringBuffer s = new StringBuffer();
- System.out.println(m.replaceFirst("app"));// app-cd%34
23、public Matcher usePattern(Pattern newPattern)
更改匹配器的匹配模式。
测试:
- public static void main(String[] args) {
- Pattern p = Pattern.compile("[a-z]+");
- Matcher m = p.matcher("111aaa222");
- System.out.println(piPei(m));// (模式[a-z]+):匹配子串:aaa;开始位置:3;结束位置:6;
- m.usePattern(Pattern.compile("\\d+"));
- System.out.println(piPei(m));// (模式\d+):匹配子串:222;开始位置:6;结束位置:9;
- }
- public static String piPei(Matcher m) {
- StringBuffer s = new StringBuffer();
- while (m.find()) {
- s.append("匹配子串:" + m.group() + ";");
- s.append("开始位置:" + m.start() + ";");
- s.append("结束位置:" + m.end() + ";");
- }
- if (s.length() == 0) {
- s.append("没有匹配到!");
- }
- s.insert(0, "(模式" + m.pattern().pattern() + "):");
- return s.toString();
- }
可以看到更改匹配模式为"\\d+"后,只匹配到了"222",若需要匹配所有数字字符,应对匹配器初始化。
- Pattern p = Pattern.compile("[a-z]+");
- Matcher m = p.matcher("111aaa222");
- System.out.println(piPei(m));// (模式[a-z]+):匹配子串:aaa;开始位置:3;结束位置:6;
- m.usePattern(Pattern.compile("\\d+"));
- m.reset();
- System.out.println(piPei(m));// (模式\d+):匹配子串:111;开始位置:0;结束位置:3;匹配子串:222;开始位置:6;结束位置:9;
更多与正则表达式相关内容:
java正则表达式之Greedy、Reluctant和Possessive
java之Matcher类详解的更多相关文章
- java之StringBuffer类详解
StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...
- java之AbstractStringBuilder类详解
目录 AbstractStringBuilder类 字段 构造器 方法 public abstract String toString() 扩充容量 void expandCapacity(in ...
- java之StringBuilder类详解
StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...
- java.lang.Thread类详解
java.lang.Thread类详解 一.前言 位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,今天我们来学习一下Thread类,在学习Thread类之前 ...
- Java中dimension类详解
Java中dimension类详解 https://blog.csdn.net/hrw1234567890/article/details/81217788
- java的ReentrantLock类详解
ReentrantLock 能用于更精细化的加锁的Java类, 通过它能更清楚了解Java的锁机制 ReentrantLock 类的集成关系有点复杂, 既有内部类, 还有多重继承关系 类的定义 pub ...
- JAVA正则表达式:Pattern类与Matcher类详解(转)
java.util.regex是一个用正则表达式所订制的模式来对字符串进行匹配工作的类库包.它包括两个类:Pattern和Matcher Pattern 一个Pattern是一个正则表达式经编译后的表 ...
- JAVA正则表达式:Pattern类与Matcher类详解
java.util.regex是一个用正则表达式所订制的模式来对字符串进行匹配工作的类库包.它包括两个类:Pattern和Matcher Pattern 一个Pattern是一个正则表达式经编译后的表 ...
- [转] JAVA正则表达式:Pattern类与Matcher类详解(转)
java.util.regex是一个用正则表达式所订制的模式来对字符串进行匹配工作的类库包.它包括两个类:Pattern和 Matcher Pattern 一个Pattern是一个正则表达式经编译后的 ...
随机推荐
- SpringBoot 基础(二)
目录 SpringBoot基础(二) 一.操作数据库 1. SpringBootJdbc 2. SpringBoot 整合 Mybatis 3. SpringBott 使用JPA 二.使用 Thyme ...
- Akka-CQRS(11)- akka-http for http-web-service: Marshalling-数据序列化
前面几篇讨论了关于gRPC方式的前后端连接集成方式.gRPC也是一个开放的标准,但讲到普及性就远远不及基于http/1.1协议的web-service了.特别是gRPC的前端编程还是有一定的门槛,所以 ...
- Java基础扫盲系列(-)—— String中的format
Java基础扫盲系列(-)-- String中的format 以前大学学习C语言时,有函数printf,能够按照格式打印输出的内容.但是工作后使用Java,也没有遇到过格式打印的需求,今天遇到项目代码 ...
- Syste.IO命名空间下的流操作类之间的关系
- Java自学-操作符 关系操作符
Java的关系操作符 关系操作符:比较两个变量之间的关系 > 大于 >= 大于或等于 < 小于 <= 小于或等于 == 是否相等 != 是否不等 示例: public clas ...
- toString()和Object.prototype.toString.call() 不一样
var arr=[1,2,3];arr.toString()//输出“1,2,3”Object.prototype.toString.call(arr)//输出 "[object Array ...
- Web前端2019面试总结4
1.span标签的width和height分别为多少? 首先span不是块级元素,是不支持宽高的,但是style中有了个float:left:就使得span变成了块级元素支持宽高,height ...
- Cheat Engine 字节数组类型
BIG5 编码:http://www.qqxiuzi.cn/zh/hanzi-big5-bianma.php 打开游戏 准备修改名字 查找BIG5码 藤 吉 开始扫描 使用字节数组类型扫描 新BIG5 ...
- springboot使用阿里fastjson来解析数据
1.spring boot默认使用的json解析框架是jackson,使用fastjson需要配置,首先引入fastjson依赖 pom.xml配置如下: <project xmlns=&quo ...
- ML- 线性回归推导
线性回归, 这部分算是我最为擅长的了, 真的不吹, 6年经验, 我高中时代就已经会推导了, 当然是最最小二乘法和统计学(假设检验, 参数分布等)的角度. 后来上了大学, 又是从最小二乘和统计学角度, ...