初识Java 16-2 字符串

news2024/11/17 3:49:44

目录

正则表达式

简单的介绍

正则表达式的创建

量词

Pattern和Matcher

1. find()

2. 分组

3. start()和end()

4. compile()中的标记

5. split()

6. 替换操作

reset()

正则表达式和Java的I/O


本笔记参考自: 《On Java 中文版》


正则表达式

        正则表达式是强大而灵活的文本处理工具,它提供了一种紧凑且动态的语言,来解决字符串的各种问题。通过正则表达式,我们能够以编程的方式构建复杂的文本模式,从而在字符串中进行寻找和处理。

简单的介绍

        以数字为例,已知两点信息:

  1. 在正则表达式中,【\d】表示的是一个数字(0 ~ 9)。
  2. 而在Java中,【\】是一个转义字符。因此【\\】表示的才是一个反斜杠。

        把上述的这两点结合起来可知,若想要在Java中用正则表达式表示一个数字,那么我们应该使用的(正则表达式)字符串应该是【\\d】。同理可得,若想要往一个正则表达式中插入一个反斜杠,那么其对应的字符串应该是【\\\\】。

    可以发现,正则表达式在Java中的表现过于笨拙,这是因为Java一开始并没有考虑到正则表达式。

(不过换行符【\n】和制表符【\t】都只需要一个反斜杠)

        下面的例子用以显示普通字符串反斜杠和正则表达式反斜杠的区别,这会用到String.matches()函数:

matches()的参数就是一个正则表达式,它会作用于调用matches()的字符串。

【例子:正则表达式的反斜杠】

public class BackSlashes {
    public static void main(String[] args) {
        String one = "\\"; // 表示一个反斜杠
        String two = "\\\\"; // 表示两个反斜杠
        String three = "\\\\\\\\"; // 表示三个反斜杠

        System.out.println("===  普通的反斜杠  ===");
        System.out.println(one);
        System.out.println(two);
        System.out.println(three);

        System.out.println("===与正则表达式中的【\\】进行比较===");
        System.out.println(one.matches("\\\\"));
        System.out.println(two.matches("\\\\\\\\"));
        System.out.println(three.matches("\\\\\\\\\\\\\\\\"));
    }
}

        程序执行的结果是:

        若我们需要匹配一个正则表达式的反斜杠,则需要四个反斜杠。

        再看一个例子,这次的正则表达式会比较复杂:

【例子:更复杂的正则表达式】

public class IntegerMatch {
    public static void main(String[] args) {
        System.out.println("-1234".matches("-?\\d+"));
        System.out.println("-5678".matches("-?\\d+"));
        System.out.println("+514".matches("-?\\d+"));
        System.out.println("+514".matches("(-|\\+)?\\d+"));
    }
}

        程序执行的结果是:

        首先解释一下正则表达式【-?\\d+】。可以将其拆分为几个部分:

  • -】:表示一个减号。
  • ?】:这个字符前面的元素可以出现一次或多次。
  • \\d】:正如之前所说,表示的是与一个数字进行匹配。
  • +】:表示前面的元素有一个或多个。

        更进一步,【(-|\\+)?\\d+】只是在上面的表达式上多加了一些字符:

  • ()】:将表达式分组。
  • |】:表示“或”操作。
  • \\+】:就如上面提到的,+在正则表达式中有特殊的含义,因此若想要匹配一个+,就需要使用反斜杠进行转义。

        String类内置了一个很有用的正则表达式工具split(),它会根据参数(一个正则表达式)切割字符串:

【例子:使用split()切割字符串】

import java.util.Arrays;

public class Spliting {
    public static String bird =
            "Oh, to be a bird so free! " +
                    "To soar through the sky with ease......" +
                    "Unfettered by the chains of earth, " +
                    "Is a dream that fills my soul. ";

    public static void split(String regex) {
        System.out.println(
                Arrays.toString(bird.split(regex)));
    }

    public static void main(String[] args) {
        split(" "); // 参数不一定要有正则字符
        split("\\W+"); // 按照不是单词的字符切割
        split("n\\W+"); // n之后跟着一个不是单词的字符
    }
}

        程序执行的结果是:

        注意:并非只有特殊字符能够组成正则表达式,也可以添加普通字符。

        上述程序中出现了【\\W】,这里先介绍它和一个与之类似的正则表达式:

  • \\W】:表示非单词字符。
  • \\w】:表示单词字符。

    String.split()还有一个重载版本,它限制了拆分发生的次数。

        正则表达式也可用于替换操作:

【例子:使用正则表达式进行替换】

public class Replacing {
    static String s = Spliting.bird;

    public static void main(String[] args) {
        System.out.println(
                s.replaceFirst("f\\w+", "【filter】"));
        System.out.println(
                s.replaceAll("free|the|a", "【filter】"));
    }
}

        程序执行的结果是:

    若正则表达式需要被多次使用,非字符串表达式会具有更高的效率。

正则表达式的创建

        Java中存在着专门处理正则表达式的类Pattern,这个类位于java.util.regex包中。这个类包含了数个用于适用于正则表达式的方法。官方文档中详细地描述了各种特殊字符的用法,并且进行了分类。这里简单介绍一些常用的特殊字符及其使用:

符号结果 / 含义
构造项A指定字符A
\xhh匹配一个两位十六进制数(\x00 ~ \xFF)表示的字符
\uhhhh匹配一个四位十六进制数表示的Unicode字符
\t制表符(即Tab)
\n换行
\r回车
\f换页
\e转义(即escape)
字符模式.可以匹配任何一个字符
[abc]ab c 中的任何一个字符(与a|b|c相同)
[^abc]ab c 之外的任何字符
[a-zA-Z]a ~ z A ~ Z 的任何字符
[abc[hij]]abchij 中的任何一个字符(求并集)
[a-z&&[hij]]hij 中的任何一个字符(求交集)
\s一个空白字符(即空格、制表符、换行符、换页、回车)
\S非空白字符(即 [^\s]
\d一个数字(即 [0-9]
\D非数字(即 [^0-9]
\w一个单词字符(即 [a-zA-Z_0-9]
\W一个非单词字符([^\w]
逻辑操作符XYX后跟着一个Y
X|YXY
X&YX且Y
边界匹配器^

匹配输入的开始。若多行标志(?m)被设置,则表示为行的开始。([^n]见下一栏)

例如:

        "^A"会匹配"An E"中的A

$匹配输入的结束。若多行标志(?m)被设置,则表示为行的结束。
\b

表示单词的边界(不是\d)。

例如:

        "\\bm"会匹配"moon"中的"m"。但"\\boo"不会匹配"moon"中的"oo"

\B匹配一个“非单词边界”,具体情况可以参考该指南
\G前一个匹配的结束
通配符和量词*匹配前面的子表达式零次或多次
+匹配前面的子表达式一次或多次,被称为贪婪量词
?匹配前面的子表达式零次或一次,被称为非贪婪量词
{n}

若前面的字符出现(且只出现)了n次,则继续匹配。

例如:

        "a{2}"不会匹配"candy"中的'a',但是会匹配"caandy"中的所有'a'

{n,}

若前面的字符至少出现了n次,则进行匹配。

例如:

        "a{2},"可以匹配"aa""aaa""aaaa",但是不会匹配"a"

{n,m}

匹配的字符最少出现n次,最多只能出现m次。

例如:

        "a{2,3}"匹配"caandy"中的两个'a',匹配"caaaaandy"中的前三个'a'

[a]

一个字符集合,匹配方括号内的任意字符(包括转义序列

        例如:"[abc]""[a-c]"一样,匹配"bank"中的'b',也能匹配"city"中的'c'

[^a]

表示反向的字符集合。

        例如:"[^a]"匹配除'a'之外的所有字符。

(n)作为一个分组,可在之后的表达式中用 \i 来引用第 i 个分组(官方文档将圆括号放在了逻辑操作符一栏,笔者放在这里是为了方便自己归纳)

【例子:不同的正则表达式匹配】

public class Rudolph {
    public static void main(String[] args) {
        for (String pattern : new String[]{
                "Rudolph",
                "[rR]udolph",
                "[rR][aeiou][a-z]ol.*",
                "R.*"}) {
            System.out.println("Rudolph".matches(pattern));
        }
    }
}

        程序执行的结果是:

        注意:我们的目标不应该是创建多么复杂的正则表达式,而应该是创建能够完成工作的最简单的正则表达式。


量词

        量词描述了一个正则表达式处理输入文本的方式。

  • 贪婪型:量词默认是贪婪的。这种形式的表达式会为模式找到尽可能多的匹配项,但这也有可能引发问题。
  • 勉强型:也被称为惰性匹配非贪婪匹配。这种形式使用问号指定,量词会匹配满足模式所需的最少字符数。
  • 占有型:占有型量词不会保留正则表达式的中间状态,以防止回溯。这种形式可以防止正则表达式在运行时失控,使执行更加有效。

    正则表达式在应用于字符串时,会产生许多状态,方便在匹配失败时回溯。占有型量词会处理这种情况。

        接下来通过表格的形式介绍一些量词的用法:

贪婪型勉强型占有型匹配 / 解释
X?X??X?+X(一个或没有)
X*X*?X*+X(零个或多个)
X+X+?X++X(一个或多个)
X{n}X{n}?X{n}+精确匹配n个X
X{n,}X{n,}?X{n,}+至少匹配n个X
X{n,m}X{n,m}?X{n,m}+至少匹配n个X,并且至多匹配m

        注意:这些表达式最好都用括号括起来,防止出现误解。例如:

abc+

看起来会匹配一个或多个abc序列,但实际上它的意思是ab后面跟着多个c。这是很容易忽略的,假如我们匹配的字符串正好是"abcabc",那么它似乎就没有出错。若想要匹配多个abc序列,应该这么做:

(abc)+

PatternMatcher

       尽管我们能够通过String做到有限的操作,但正则表达式的功能并没有被充分发挥。正如之前介绍的,Java专门设计了一个Pattern类,用于表示正则表达式的编译版本:

        首先介绍两个接下来会用到的Pattern方法:

  • compile():这个方法是静态的。它能够接受一个String,并且将其编译为Pattern对象。
  • matcher():传入一个匹配对象,并生成一个Matcher对象。生成的Matcher对象中包含了各种能够对匹配对象进行的操作。

【例子:通过Pattern使用正则表达式】

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TestRegularExpression {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.format(
                    "请这样进行输入:%n" +
                            "\tjava TestRegularExpression " +
                            "正则表达式序列%n");
            System.exit(0);
        }
        System.out.println("已输入:\"" + args[0] + "\"");
        for (String arg : args) {
            System.out.println(
                    "当前正则表达式:\"" + arg + "\"");
            Pattern p = Pattern.compile(arg); // 通过Pattern构建正则表达式对象
            Matcher m = p.matcher(args[0]);
            while (m.find()) { // 尝试查找下一个能够匹配的项
                System.out.println(
                        "匹配成功,\"" + m.group() + "\"的位置是" +
                                m.start() + "-" + (m.end() - 1));
            }
        }
    }
}

        通过命令行键入:

java TestRegularExpression 114514 11 1+ [1+] \(14\){2,}

        可得执行结果是:

        通常,我们会通过Pattern.matcher()方法生成Matcher对象,然后再通过Matcher对象完成各种匹配操作。

        再介绍两个常用的Pattern方法:

  • matches():是一个静态方法,包含两个参数:正则表达式和匹配对象的序列。这个方法会尝试通过正则表达式进行匹配,返回的结果就表示匹配是否成功。
  • split():根据输入序列的匹配结果,拆分字符串。

    此处对Pattern和Matcher的介绍较为简单,可以查看文档获得更多信息(Pattern、Matcher)。

        下面介绍的是Matcher中的常用方法:

1. find()

        该方法会尝试查找与模式匹配相适应的下一个子序列(这就像一个迭代器,向前遍历输入的字符串)。若不输入参数,则find()会从头开始进制查找。除此之外,它还有一个重载方法:

该方法可以重置匹配的索引,从新的起点再次开始匹配。

【例子:find()的使用】

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Finding {
    public static void main(String[] args) {
        Matcher m = Pattern.compile("\\w+")
                .matcher("In the boundless universe");

        while (m.find())
            System.out.print(m.group() + " ");
        System.out.println();
        int i = 0;
        while (m.find(i)) {
            System.out.print(m.group() + " "); // group()会返回当前
            i++;
        }
    }
}

        程序执行的结果是:

------

2. 分组

        Matcher包含了一组关于分组的方法,在介绍它们之前先要了解什么是分组。分组,即用括号括起来的正则表达式,之后可以在代码中通过分组号调用它们:

  • 分组0:表示整个表达式;
  • 分组1:表示第一个带括号的分组;
  • ……

例如,下面的表达式中有三个分组:

A(B(C))D

其中,分组0是【ABCD】,分组1是【BC】,分组2是【C】。

        Matcher提供了一系列方法,用于获取分组的信息(详见文档):

  • groupCount()

    返回匹配模式中的分组个数,但不包括分组0

下面的这个方法有个不传参数的重载,用于直接返回上一次匹配的结果。

  • group(int group)

    输入参数是分组的索引。返回指定分组在上一次匹配操作期间捕获的输入子序列。若匹配失败,或返回空序列,则返回null

【例子:对序列进行分组】

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Groups {
    public static final String POEM =
            "I wandered lonely as a cloud\n" +
                    "That floats on high o'er vales and hills,\n" +
                    "When all at once I saw a crowd,\n" +
                    "A host, of golden daffodils;\n" +
                    "Fluttering and dancing in the breeze.\n" +
                    "Nature made them pure and fair;\n" +
                    "Their beauty struck my heart with gladness,\n" +
                    "And I stopped there in wonder.";

    public static void main(String[] args) {
        // (?m)用于配置正则表达式,表示进行多行匹配
        // 在这里(?m)用于改变$的行为,使其能够匹配文本行的结束
        // \s匹配空白字符,\S匹配非空白字符,$匹配行的结束
        Matcher m = Pattern.compile(
                        "(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$")
                .matcher(POEM);
        while (m.find()) {
            for (int i = 0; i < m.groupCount(); i++)
                System.out.print("[" + m.group(i) + "]");
            System.out.println();
        }
    }
}

        程序执行的结果是:

        由于$的默认行为是与整个序列(即整个POEM)的末尾进行匹配。所以这里需要使用模式标记(?m)改变$的行为,使其能够注意到输入中的换行符。

------

3. start()end()

        与group()类似,这两个方法也有不传参数的版本,用于直接匹配上一次的匹配项。这里介绍带有参数的版本:

  • start(int group)

    同样拥有一个输入参数用于指定分组的索引。返回指定组在上一次匹配操作期间捕获序列的开始索引。
  • end(int group)

    参数与start()相同。返回指定组在上一次匹配操作期间捕获序列的最后一个字符之后的偏移量。

【例子:匹配,并且打印索引】

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StartEnd {
    public static String input =
                    "But here were fifty of them,\n" +
                    "And they all rang true!\n" +
                    "And then I knew not what to do,\n" +
                    "But picked and ate one daffodil,\n" +
                    "And sank into a dream.";

    private static class Display {
        private boolean regexPrinted = false;
        private String regex;

        Display(String regex) {
            this.regex = regex;
        }

        void display(String message) {
            if (!regexPrinted) {
                System.out.println("当前正则表达式:" + regex);
                regexPrinted = true;
            }
            System.out.println(message);
        }
    }

    static void examine(String s, String regex) {
        Display d = new Display(regex);
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(s);

        while (m.find())
            d.display("find() \"" + m.group() +
                    "\" start = " + m.start() + " end = " + m.end());
        if (m.lookingAt()) // 仅当输入字符串的开始部分模式匹配成功时,返回true
            d.display("lookingAt() start = " +
                    m.start() + " end = " + m.end());
        if (m.matches()) // 仅在整个输入序列都与正则表达式匹配时,返回true
            d.display("matches() start = " +
                    m.start() + " end = " + m.end());
    }

    public static void main(String[] args) {
        for (String in : input.split("\n")) {
            System.out.println("输入:" + in);
            for (String regex : new String[]{
                    "\\were", "\\w*l", "th\\w+", "And.*?[,!]"})
                examine(in, regex);
            System.out.println();

        }
    }
}

        程序执行的结果是:

         这里还展示了find()lookingAt()matches()的区别。find()能够在字符串中的任何位置匹配正则表达式。但对于另外两个方法而言:

  • lookingAt():只匹配输入字符串的开始部分。
  • matches():仅当整个输入字符串和正则表达式匹配时成立。

------

4. compile()中的标记

        Pattern中的compile()有一个重载版本,可以接受一个标记,并根据标记来改变匹配行为。

其中,flag能够接受的参数已经在Pattern中进行了规定:

标记效果
CASE_INSENSITIVE(?i)进行模式匹配时,不考虑大小写(若不启用,则只有US_ASCII字符集的匹配不考虑大小写)。
MULTILINE(?m)启用多行模式。使得表达式^$可以分别匹配一行的开头和结尾。
DOTALL(?s)在该模式下,表达式【.】可以匹配任何字符,包括换行符(默认情况下不匹配)。
UNICODE CASE(?u)需要配合标记CASE_INSENSITIVE进行使用。此时不区分大小写的匹配将以符合Unicode标准的形式完成。
CANON EQ

开启后,当且仅当两个字符的完全正则分解匹配时,才认为它们匹配。

例如:启用标记后,表达式【\u003F】将匹配字符串【?】。

UNIX LINES(?d)在该模式下,在【.】、【^】和【$】的行为中,只识别换行符【\n】。
LITERAL该模式会将输入字符串视为一个文本,不对任何特殊字符进行识别。
COMMENTS(?x)在该模式下,空白符和以【#】开头的嵌入注释会被忽略,直到行尾。

        上述表格中,用红色突出的标记是较常用的

        另外,可以在正则表达式中直接使用上述表格中的大多数标记,仅需将标记一栏中括号内的字符应用于希望生效的位置即可。

        这些标记可以组合使用:

【例子:使用“或”操作【|】组合标记】

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ReFlags {
    public static void main(String[] args) {
        Pattern p = Pattern.compile("^java", // 匹配每一行以java开头的文本,忽略大小写
                Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
        Matcher m = p.matcher(
                "java has regex\nJava has regex\n" +
                        "JAVA has pretty good regular expression\n" +
                        "Regular expressions are in Java");
        while (m.find())
            System.out.println(m.group());
    }
}

        程序执行的结果是:

------

5. split()

        split()方法可以根据正则表达式拆分字符串:

在拆分完毕后,会返回操作完毕的字符串对象数组。

        除此之外,split()方法还有一个重载:

这个版本添加的limit参数可用于限制拆分的次数。

【例子:拆分字符串】

import java.util.Arrays;
import java.util.regex.Pattern;

public class SplitDemo {
    public static void main(String[] args) {
        String input =
                "啊啊!!今天天气真好!!红的花!!绿的草";

        System.out.println(Arrays.toString(
                Pattern.compile("!!").split(input)));

        // 限制执行次数的版本:
        System.out.println(Arrays.toString(
                Pattern.compile("!!").split(input, 2)));
    }
}

        程序执行的结果是:

------

6. 替换操作

        这里介绍三个用于替换文本的方法(这些方法都存在于Matcher中):

  • replaceFirst(String replacement)

    将参数replacement与字符串第一个匹配的部分进行替换。
  • replaceAll(String replacement)

    将参数replacement与字符串每个匹配的部分进行替换。
  • appendReplacement(StringBuffer sbuf, String replacement)

    进行逐步替换,并将得到的结果保存在sbuf中。这个方法的灵活性在于,我们可以调用其他方法来处理或生成replacement,为每一个需要替换的地方输入特定的替换字符串。

(关于上述三个方法的重载,详见Matcher的文档)

    除此之外,appendReplacement()也可以和appendTail()一起使用,后者将不用特殊处理的剩余字符串复制到sbuf中。

【例子:各种替换操作】

/*! 这块文本块将被用于本程序的Matcher的匹配。
    这行文本块通过特殊的注释符进行修饰……
 !*/

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class TheReplacements {
    public static void main(String[] args)
            throws Exception {
        String s = Files.lines(
                Paths.get("TheReplacements.java"))
                .collect(Collectors.joining("\n"));

        // 对上面注释掉的文本块进行匹配
        Matcher mInput = Pattern.compile(
                "/\\*!(.*)!\\*/", Pattern.DOTALL).matcher(s);
        if (mInput.find())
            s = mInput.group(1); // 被分组1(.*)捕获的
        s = s.replaceAll(" {2,}", " "); // 用一个空格替代多个空格
        s = s.replaceAll("(?m)^ +", ""); // 删除每行开头的多个空格
        System.out.println(s);

        s = s.replaceFirst("文本块", "【WenBenKuai】");
        StringBuffer sbuf = new StringBuffer();
        Pattern p = Pattern.compile("[aeiou]");
        Matcher m = p.matcher(s);
        // 一边查找,一边替换
        while (m.find())
            m.appendReplacement(
                    sbuf, m.group().toUpperCase());
        m.appendTail(sbuf);
        System.out.println(sbuf);
    }
}

        程序执行的结果是:

        appendReplacement()还允许在第二个参数(即替换的字符串)中使用【\$g】直接引用匹配的某个组,其中g就是组号。不过这种处理较为简单,无法胜任更复杂的字符串操作。

------

reset()

        reset()方法允许我们使用新的序列重置匹配器。换言之,我们可以将现有的Matcher应用于新的序列。

【例子:复用Matcher

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Resetting {
    public static void main(String[] args)
            throws Exception {
        Matcher m = Pattern.compile("[frb][aiu][gx]")
                .matcher("fix the rug with bags");
        while (m.find())
            System.out.print(m.group() + " ");
        System.out.println();

        m.reset("fix the rig with rags");
        while (m.find())
            System.out.print(m.group() + " ");
    }
}

        程序执行的结果是:

    reset()还有一个无参的版本,重置匹配器,但不使用新的序列。

正则表达式和Java的I/O

        也可以动态地运用正则表达式,在各种文件中搜索匹配项。下面的例子模仿了UNIX的grep命令。

【例子:模仿grep命令】

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JFrep {
    public static void main(String[] args)
            throws Exception {
        if (args.length < 2) {
            System.out.println(
                    "请按格式输入:\n" +
                            "\tjava JGrep 文件名 正则表达式");
            System.exit(0);
        }

        Pattern p = Pattern.compile(args[1]);
        Matcher m = p.matcher("");
        // 遍历输入文件的每一行
        Files.readAllLines(Paths.get(args[0])).forEach(
                line -> {
                    m.reset(line);
                    while (m.find())
                        System.out.println(
                                m.group() + ": " + m.start());
                }
        );
    }
}

        执行命令:

java JFrep Hex.java "return|for|String"

可得到输出为:

        在这里,我们使用reset()在每一次匹配完成后重置Matcher对象。尽管也能在forEach()内为每一次匹配单独设置一个Matcher对象,但这种做法会有更大的开销。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1152095.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深入浅出汇编语言】寄存器精讲第二期

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、算法模板、汇编语言 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️物理地址二. ⛳️16位结构的CPU三. ⛳️8086CPU给出物理地址的方…

你被骗了吗?别拿低价诱骗机器视觉小白,4000元机器视觉系统怎么来的?机器视觉工程师自己组装一个2000元不到,还带深度学习

淘宝闲鱼&#xff0c;大家搜搜铺价格&#xff0c;特别是机器视觉小白。 机架&#xff1a;&#xff08;新的&#xff09;200元以下。(看需求&#xff0c;自己简单打光&#xff0c;买个50元的。如果复杂&#xff0c;就拿给供应商免费打光) 相机&#xff0c;镜头&#xff1a;&am…

有效的数独

题目链接 有效的数独 题目描述 注意点 board.length 9board[i].length 9board[i][j] 是一位数字&#xff08;1-9&#xff09;或者 ‘.’ 解答思路 首先判断行是否满足数独条件&#xff0c;再判断列是否满足数独条件&#xff0c;最后再判断划分的3x3方格是否满足数独条件…

2.数据结构-链表

概述 目标 链表的存储结构和特点链表的几种分类及各自的存储结构链表和数组的差异刷题(反转链表) 概念及存储结构 先来看一下动态数组 ArrayList 存在哪些弊端 插入&#xff0c;删除时间复杂度高需要一块连续的存储空间&#xff0c;对内存要求比较高&#xff0c;比如要申请…

SSM培训报名管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 培训报名管理系统是一套完善的信息系统&#xff0c;结合SSM框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主 要采用B/S模式开…

vim

简介 vim是一款多模式的文本编辑器&#xff0c;vim里面还有很多子命令&#xff0c;来进行代码的编写操作 常用模式图 命令模式 光标移动 shif $ 光标定义到当前行的最右侧结尾 shift ^ 光标定义到当前行的最左侧开头 shift g 光标定位到文本最末尾…

激光雷达点云基础-点云滤波算法与NDT匹配算法

激光雷达点云处理在五年前就做了较多的工作&#xff0c;最近有一些新的接触发现激光雷达代码原理五年前未见重大更新&#xff0c;或许C与激光雷达结合本身就是比较高的技术门槛。深度学习调包侠在硬核激光雷达技术面前可以说是完全的自愧不如啊。 1、点云滤波 在获取点云数据…

利用远程IO模块,轻松驾驭食品包装生产的自动化

常见的自动化包装系统&#xff0c;它的核心部分通常由一系列高端设备组成&#xff0c;包括自动开箱机、自动封箱机、自动捆扎机、装箱机器人、码垛机器人等。这些设备协同工作&#xff0c;形成一条高效运转的生产线&#xff0c;从开箱到装箱&#xff0c;再到码垛&#xff0c;每…

SpringCloud(二) Eureka注册中心的使用

在SpringCloud(一)中,我们学会了使用RestTemplate进行远程调用,但是在调用user-service时候需要在order-service中发送http请求,请求中需要书写对应微服务的ip和端口号,十分不方便,如果此时有多个user-service实例的话,就不知道调用哪个了(除非每次调用的时候都对ip和端口号进行…

SpringCloud 微服务全栈体系(九)

第九章 Docker 三、Dockerfile 自定义镜像 常见的镜像在 DockerHub 就能找到&#xff0c;但是我们自己写的项目就必须自己构建镜像了。 而要自定义镜像&#xff0c;就必须先了解镜像的结构才行。 1. 镜像结构 镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而…

uniapp保存网络图片

先执行下载uni.downloadFile接口&#xff0c;再执行保存图片uni.saveImageToPhotosAlbum接口。 // 保存二维码 saveQrcode() {var _this this;uni.downloadFile({url: _this.qrcodeUrl, //二维码网络图片的地址success(res) {console.log(res);uni.saveImageToPhotosAlbum({fi…

MQTT 保留消息

一、保留消息简介 官方文档&#xff1a;https://www.emqx.io/docs/zh/v5.1/messaging/mqtt-retained-message.html 1、为什么需要保留消息 不考虑持久会话的因素&#xff0c;MQTT订阅只有在客户端连接之后才能创建主题。所以当消息到达服务端之后&#xff0c;服务端只会把消息…

Pycharm安装jupyter和d2l

安装 jupyter: jupyter是d2l的依赖库&#xff0c;没有它就用不了d2l pycharm中端输入pip install jupyter安装若失败则&#xff1a; 若网速过慢&#xff0c;则更改镜像源再下载&#xff1a; pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ pip …

梯度下降|笔记

1.梯度下降法的原理 1.1确定一个小目标&#xff1a;预测函数 机器学习中一个常见的任务是通过学习算法&#xff0c;自动发现数据背后的规律&#xff0c;不断改进模型&#xff0c;做出预测。 上图的坐标系&#xff0c;横轴表示房子面积&#xff0c;纵轴表示房价&#xff0c;图…

注册资金认缴和实缴的区别

公司注册资本金实缴和认缴的区别有性质不同、意义不同、类型不同、缴纳时限不同、缴纳方式不同、包含范围不同等方面。 具体分析如下&#xff1a; 公司注册资本实缴制是营业执照注册资金有多少&#xff0c;该公司的银行验资账户上就得有相应金额的资金。实缴制需要占用公司的资…

__attribute__中的constructor和destructor--如何让程序退出时调用指定函数

背景 假设你在开发一个基础组件x&#xff0c;然后你设计了一个x_init接口用来初始化这个组件&#xff0c;相应地你设计了一个x_deinit来去初始化。这样其它模块要用到这个组件时&#xff0c;先调一下x_init, 用完了再调一下x_deinit。init和deinit这是一对很常见的接口&#x…

NB-IOT的粮库挡粮门异动监测装置

一种基于NBIOT的粮库挡粮门异动监测装置,包括若干个NBIOT开门监测装置,物联网后台管理系统,NBIOT低功耗广域网络和用户访问终端;各个NBIOT开门监测装置通过NBIOT低功耗广域网络与物联网后台管理系统连接,物联网后台管理系统与用户访问终端连接.NBIOT开门监测装置能够对粮库挡粮…

网络新闻发稿为何经久不衰?

有的老板可能看不到新闻营销的直接回报&#xff0c;一直不乐意在此方面投入&#xff0c;但是却看到竞争对手一直在搞新闻营销&#xff0c;也就安排个PR做做新闻公关。小马识途营销顾问观察&#xff0c;自互联网诞生以来&#xff0c;新闻营销一直是网络营销工作中的一个重点。 如…

JS中的元编程

ES6&#xff08;ECMAScript 2015&#xff09;新增了对 Reflect 和 Proxy 对象的支持&#xff0c;使得我们能够便捷地进行元编程。让我们通过示例来学习它们的用法。 1、什么是元编程 元编程 无异于 编程中的魔法&#xff01;如果编写一个“能够读取、修改、分析、甚至生成新程…

提高小程序SEO 排名,9招优化技巧!

在当今移动互联网时代&#xff0c;小程序已经成为企业必不可少的一种营销手段&#xff0c;而如何让用户能够更容易地找到自己的小程序&#xff0c;就需要进行SEO优化&#xff0c;提升小程序的排名&#xff0c;本文将 为大家介绍9个小程序SEO优化技巧&#xff0c;帮助您的小程序…