java 前缀树的实现,敏感词的匹配和标记

news2024/9/22 5:38:03

目录

  • 一、前缀树的介绍和定义
    • 1.前缀树的定义
    • 2.前缀树的结构
  • 二、前缀树的实现
    • 1.向前缀树中增加词语
    • 2.向前缀树中删除词语
    • 3.对于使用前缀树进行词语标识:
    • 4.前缀树的实现代码
  • 三、前缀树使用及测试
    • 1.向前缀树上增加词语
    • 2.根据输入匹配前缀树上的词语
    • 3.判断前缀树上是否有某个词语
    • 4.判断输入的字符串是否是前缀树上某个词语的开头
    • 5.从前缀树上删除某个词语
    • 6.使用前缀树进行敏感词过滤
    • 7.使用前缀树对文章、消息进行敏感词标记

一、前缀树的介绍和定义

前缀树是一种多叉树结构,经常用于快速检索匹配、敏感词匹配替换、文章多词语匹配查找高亮等场景。

1.前缀树的定义

(1).前缀树的根节点不包含字符;

(2).前缀树上除叶子节点外,任意一个节点都包含一个字符,并且任意节点下都可能含有n个子节点;

(3).前缀树上除根节点外每个节点都应有一个标识来表明其是否是一个词的结尾;

(4).前缀树上的叶子节点一定是一个词的结尾;

2.前缀树的结构

假如我有如下14个词语:

“菜鸡”,“菜狗”,“张三丰满”, “张三丰”, “张三”, “张三丰是狗”, “张全”,
“张力”, “张力懦夫”,“王五”,“王五王八”, “王六滚蛋”,“滚蛋”, “滚”

将每个词语拆分后将其放入前缀树上,那么这棵前缀树看起来像是这样的:

请添加图片描述

在这棵前缀树上如果该节点的字符是一个词语的结尾我就用白色字体表示,即如果该节点的字符颜色是白色的那么从根节点到该节点的所有节点连起来是一个词语。

比如:
从根节点开始,“鸡” 这个节点的字符是白色的,说明从根节点到这个节点是一个词语,也就是 “菜鸡” 是一个词语;
同理,“五” 是白色的,那 “王五” 就是个词语;
“八” 是白色的,那 “王五王八” 就是个词语;

所以如果给出一个像上面的前缀树,可以容易的反推出前缀树上有 “菜鸡”、“张三”、“张三丰”、“张三丰满”、“滚” 、 “滚蛋” 等词语。

二、前缀树的实现

一般经常用 数组 或者 HashMap 来存储节点的子节点,在这里我用 HashMap 来存储节点的子节点,这样可以很快的对子节点进行搜索。

1.向前缀树中增加词语

(1)如果该词语每个字符节点都不存在于前缀树上,则从根节点开始加入该词语的每个字符节点,并将最后一个字符节点的词语标识设为 true;

(2)如果该词语的开头部分字符在前缀树上,前缀树上找到其节点,将词语剩余字符节点加入其节点下,并将最后一个字符节点的词语标识设为 true;

(3)如果该词语是前缀树上另一个词语的开头部分,在前缀树上找到要添加词语的最后字符节点,将其词语标识设为 true;

2.向前缀树中删除词语

有多种情况,需要判断要删除的词语是否在前缀树上,如果要删除的词语在前缀树上:

(1)若要删除的词语其最后一个字符子节点下没有节点,并且该词语的其他子节点下都只有一个子节点,那么可以从根节点删除此词语;或者该词语只是一个字,且其下没有子节点,那么也可以从根节点删除此词语;

例如:前缀树上张开头的词语只有一个  "张力懦夫" ,如果要删除该词语可以从根节点直接删除。

(2)若最后一个字符节点下有子节点,那么要将该最后一个字符节点的词语标识设为false;

例如:前缀树上张开头的词语只有两个  "张力懦夫" 和 "张力懦夫啊" ,如果要删除词语 "张力懦夫" 只能将 "夫" 这个节点下的词语标识设置为 false,不能删除任何节点,否则会影响另一个词。

(3)若该词语的最后一个字符节点下没有子节点,并且离最后一个字符节点最近的其他节点的子节点数有大于1或有词语标识为true 的,那么可以从那个节点把要删除词语的剩余字符节点从前缀树删除;

例如:前缀树上张开头的词语只有两个 "张力" 和 "张力懦夫啊" ,要删除"张力懦夫啊",只能删除 "力" 这个字符节点下的 "懦夫啊" ;
前缀树上张开头的词语只有两个 "张力懦夫吗" 和 "张力懦夫啊" ,要删除"张力懦夫啊",只能删除 "啊" 这个字符节点;

3.对于使用前缀树进行词语标识:

需要区分一个词语中是否包含另一个词,如果包含的话需要找到最长的那个词来标记、

如:前缀树上有 "张力" 、 "张力懦夫啊"、"王五" 三个词,需要标记的文本是 "王五张力是谁?王五张力懦夫啊",最终标记的结果是 "【王五】【张力】是谁?【王五】【张力懦夫】啊" ,而不能是 "【王五】【张力】是谁?【王五】【张力】懦夫啊"

其他前缀树敏感词替换、判断词语是否在前缀树比较简单,这里不再进行说明。

4.前缀树的实现代码



import java.util.*;

/**
 * 前缀树
 */
public class TrieTree {

    /**
     * 前缀树总的词语数
     */
    private int size;

    /**
     * 根节点
     */
    private Node root = new Node(new HashMap<>(), false);


    private static class Node {
        private Map<Character, Node> childNodeMap;
        private boolean isWord;

        private Node(Map<Character, Node> childNodeMap, boolean isWord) {
            this.childNodeMap = childNodeMap;
            this.isWord = isWord;
        }

        public void setIsWord(boolean isWord) {
            this.isWord = isWord;
        }

        public boolean getIsWord() {
            return isWord;
        }

        public Map<Character, Node> getChildNodeMap() {
            return childNodeMap;
        }

        public void setChildNodeMap(Map<Character, Node> childNodeMap) {
            this.childNodeMap = childNodeMap;
        }
    }



    /**
     * 清空前缀树
     */
    public void clear() {
        root = new Node(new HashMap<>(), false);
        size = 0;
    }

    /**
     * 获取前缀树的词语数
     */
    public int size() {
        return size;
    }

    /**
     * 向前缀树中增加节点
     */
    public void add(String[] addValues) {
        if (addValues != null) {
            Arrays.stream(addValues).forEach(this::add);
        }
    }

    /**
     * 向前缀树中增加节点
     */
    public void add(List<String> addValues) {
        if (addValues != null) {
            addValues.forEach(this::add);
        }
    }

    /**
     * 向前缀树中增加节点
     */
    public void add(String str) {
        if (str == null || str.trim().isEmpty()) {
            return;
        }
        Map<Character, Node> fatherNodeMap = root.childNodeMap;
        int index = 0;
        int addValueLength = str.length();
        while (index != addValueLength) {
            char ch = str.charAt(index);
            if (fatherNodeMap.containsKey(ch)) {
                Node node = fatherNodeMap.get(ch);
                if (index != addValueLength - 1) {
                    if (node.childNodeMap == null) {
                        node.childNodeMap = new HashMap<>();
                    }
                    fatherNodeMap = node.childNodeMap;
                } else {
                    if (!node.isWord) {
                        node.setIsWord(true);
                        size++;
                    }
                }
            } else {
                if (index != addValueLength - 1) {
                    Node nodeNew = new Node(new HashMap<>(), false);
                    fatherNodeMap.put(ch, nodeNew);
                    fatherNodeMap = nodeNew.childNodeMap;
                } else {
                    Node nodeNew = new Node(null, true);
                    fatherNodeMap.put(ch, nodeNew);
                    size++;
                }
            }
            index++;
        }
    }

    /**
     * 返回前缀树上以 inputStr 开头的词语
     */
    public List<String> matchStartWord(String inputStr) {
        List<String> matches = new ArrayList<>();
        if (root.childNodeMap.size() == 0) {
            return matches;
        }
        Node node = havaWord(inputStr, root, matches);
        if (node != null) {
            getMatchStr(new StringBuilder(inputStr), new StringBuilder(""), matches, node.childNodeMap);
        }
        return matches;
    }

    private void getMatchStr(StringBuilder inputStr, StringBuilder afterStr, List<String> matches, Map<Character, Node> childNodeMap) {
        if (childNodeMap != null) {
            for (Map.Entry<Character, Node> m : childNodeMap.entrySet()) {
                Character key = m.getKey();
                StringBuilder afterStr1 = new StringBuilder(afterStr);
                afterStr1.append(key);

                Node node = m.getValue();
                boolean isWord = node.getIsWord();
                StringBuilder inputStrOriginl = new StringBuilder(inputStr);
                if (isWord) {
                    matches.add(inputStrOriginl.append(afterStr1).toString());
                }
                if (node.childNodeMap != null) {
                    if (!isWord) {
                        getMatchStr(inputStrOriginl.append(afterStr1), new StringBuilder(""), matches, node.childNodeMap);
                    } else {
                        getMatchStr(inputStrOriginl, new StringBuilder(""), matches, node.childNodeMap);
                    }
                }
            }
        }
    }


    /**
     * 从前缀树中删除词语 str
     * 如果 str 每个字符节点下都只有其下一个字符节点,并且最后一个字符节点下没有节点则可以从根节点删除这个词语,否则将词语的最后一个字符的词语标识设置为false
     */
    public void deleteWord(String str) {
        //判断前缀树上是否有该词语
        if (null != str && haveWord(str)) {
            int index = 0;
            Node deleteNode = null;
            Node startNode = root;
            char[] charArray = str.toCharArray();
            for (int i = 0; i < charArray.length; i++) {
                char ch = charArray[i];
                Map<Character, Node> childNodeMap = startNode.childNodeMap;
                if (i != charArray.length - 1) {
                    Node node = childNodeMap.get(ch);
                    if (node.childNodeMap.size() > 1 || node.isWord) {
                        deleteNode = node;
                        index = i;
                    }
                    startNode = node;
                } else {
                    Node node = childNodeMap.get(ch);
                    Map<Character, Node> childNodeMap1 = node.childNodeMap;
                    node.isWord = false;
                    if (null != childNodeMap1 && childNodeMap1.size() > 0) {
                        //如果该最后一个字符不是叶子节点,即它还有子节点
                        deleteNode = null;
                    } else {
                        //如果该最后一个字符是叶子节点
                        if (deleteNode == null) {
                            deleteNode = root;
                        } else {
                            index++;
                        }
                    }
                }
            }

            if (deleteNode != null) {
                Map<Character, Node> childNodeMap = deleteNode.childNodeMap;
                if (childNodeMap.size() > 1) {
                    childNodeMap.remove(str.charAt(index));
                } else {
                    deleteNode.childNodeMap = null;

                }
            }
            size--;
        }
    }


    /**
     * 判断前缀树上的所有词语,词语  str
     */
    public boolean haveWord(String str) {
        if (null == str) {
            return false;
        }
        List<String> list = new ArrayList<>();
        havaWord(str, root, list);
        return list.size() > 0 && str.equals(list.get(0));
    }

    /**
     * 判断前缀树上的所有词语,是否有 str 开头的
     */
    public boolean startWordWith(String str) {
        if (null == str) {
            return false;
        }
        List<String> list = new ArrayList<>();
        Node node = havaWord(str, root, list);
        if (list.size() == 0) {
            return node != null;
        }
        return list.size() == 1 && str.equals(list.get(0));
    }

    /**
     * 判断前缀树上是否有 以 inputStr 开头的词语,
     * -有,则返回 inputStr 最后一个字符的子节点,若最后一个节点没有子节点则返回最后一个字符的节点
     *      若返回的节点不为 null,且 matches 的大小为 0,则返回的是最后一个字符的子节点(即表明 inputStr 是前缀树上的一个词语的开头,如:前缀树上有 “一生一世” 这个词,但 inputStr 是 “一生一”)
     *      若返回的节点不为 null,且 matches 的大小为 1,则返回的是 inputStr 倒数第二个字符的节点(即表明 inputStr 是前缀树上的一个词语,如:前缀树上有 “一生一世” 这个词,但 inputStr 是 “一生一世”)
     * -没有,则返回 null
     */
    private Node havaWord(String inputStr, Node startNode, List<String> matches) {
        char[] charArray = inputStr.toCharArray();
        for (int i = 0; i < charArray.length; i++) {
            char ch = charArray[i];
            Map<Character, Node> childNodeMap = startNode.childNodeMap;
            if (childNodeMap.containsKey(ch)) {
                Node node = childNodeMap.get(ch);
                if (node.childNodeMap == null && i != charArray.length - 1) {
                    return null;
                }
                if (node.childNodeMap != null || charArray.length == 1) {
                    startNode = node;
                }
                if (null != matches && i == charArray.length - 1 && node.isWord) {
                    matches.add(inputStr);
                }
                if (i == charArray.length - 1 && node.isWord && node.childNodeMap == null) {
                    return null;
                }
            } else {
                return null;
            }
        }
        return startNode;
    }


    /**
     * 字符串敏感词替换,若 text 文本中的的词语有在前缀树上的,将其替换为 *
     */
    public String sensitiveWordReplace(String text) {
        return text != null ? markWordAndWordReplace(text, null, null) : text;
    }

    /**
     * 字符串敏感词标记,若 text 文本中的的词语有在前缀树上的,用 strartSymbol和 endSymbol 将其标记
     * 如:
     * 例1:
     * 假如 "张三"、"今天" 这两个词语在前缀树上
     * 当调用 markWord("张三你好,你今天过得怎么样", "【", "】");
     * 得到的结果是 "【张三】你好,你【今天】过得怎么样"
     */
    public String markWord(String text, String strartSymbol, String endSymbol) {
        if (text != null && strartSymbol != null && endSymbol != null) {
            return markWordAndWordReplace(text, strartSymbol, endSymbol);
        }
        return text;
    }

    //敏感词替换、敏感词标记通用方法
    private String markWordAndWordReplace(String text, String strartSymbol, String endSymbol) {
        Node startNode = root;
        //isMarkWord 为 true 则为敏感词标记,false 为敏感词替换
        boolean isMarkWord = strartSymbol != null && endSymbol != null;
        //存储最终的结果
        StringBuilder result = new StringBuilder();
        //若为敏感词替换(也就是isMarkWord为false)starOrCh用于存放* ,即匹配到一个字符就存入* 。若为敏感词标记,则starOrCh用于存放匹配的词
        StringBuilder starOrCh = new StringBuilder();
        //存放匹配到的字符,即匹配到一个字符就存入
        StringBuilder matchCh = new StringBuilder();
        int index = 0;
        while (index != text.length()) {
            char ch = text.charAt(index);
            Map<Character, Node> childNodeMap = startNode.childNodeMap;
            if (childNodeMap.containsKey(ch)) {
                starOrCh.append(isMarkWord ? ch : "*");
                Node node = childNodeMap.get(ch);
                if (node.childNodeMap != null) {
                    startNode = node;
                }
                if (node.isWord) {
                    StringBuilder saveTemp = new StringBuilder(starOrCh);
                    //判断该字符后的字符在前缀树上是否在该词语的后面(如:text是"张力懦夫",当前字符ch是"张",匹配到"张力"这个词语,但是"张力懦夫"也是个词语,需要向后再判断其是否在前缀树上,并判断其是否是一个词语)
                    int nextWordEndIndex = nextCh(text, index + 1, index, node);
                    if (nextWordEndIndex > index) {
                        int nextStart = index + 1;
                        StringBuilder temp = new StringBuilder();
                        temp.append(saveTemp);
                        for (int i = nextStart; i <= nextWordEndIndex; i++) {
                            char nextCh = text.charAt(i);
                            temp.append(isMarkWord ? nextCh : "*");
                            index++;
                        }
                        appendResuleClearTemp(result, temp, matchCh, starOrCh, strartSymbol, endSymbol);
                    } else {
                        //将该词语用*号替换拼接到result , 并清空 starOrCh、temMatch
                        appendResuleClearTemp(result, starOrCh, matchCh, starOrCh, strartSymbol, endSymbol);
                    }
                    //重新设置根节点为开始遍历的节点,方便下一个字符匹配
                    startNode = root;
                } else {
                    matchCh.append(ch);
                }
            } else {
                if (matchCh.length() > 0) {
                    appendResuleClearTemp(result, matchCh, matchCh, starOrCh);
                }
                Map<Character, Node> rootChildNodeMap = root.childNodeMap;
                if (rootChildNodeMap.containsKey(ch)) {
                    matchCh.append(ch);
                    starOrCh.append(isMarkWord ? ch : "*");
                    startNode = rootChildNodeMap.get(ch);
                } else {
                    result.append(ch);
                }
            }
            index++;
        }
        return result.toString();
    }

    private void appendResuleClearTemp(StringBuilder result, StringBuilder joinBuilder, StringBuilder matchCh, StringBuilder starOrCh, String strartSymbol, String endSymbol) {
        if (strartSymbol != null) {
            result.append(strartSymbol);
        }
        result.append(joinBuilder);
        if (endSymbol != null) {
            result.append(endSymbol);
        }
        matchCh.setLength(0);
        starOrCh.setLength(0);
    }

    private void appendResuleClearTemp(StringBuilder result, StringBuilder joinBuilder, StringBuilder matchCh, StringBuilder starOrCh) {
        result.append(joinBuilder);
        matchCh.setLength(0);
        starOrCh.setLength(0);
    }

    //返回从text 的 index 下标开始,node 节点下匹配到的最长词语,返回最长的词语的最后一个字符在 text 的下标,即 wordIndex
    private int nextCh(String text, int index, int wordIndex, Node node) {
        if (index <= text.length() - 1) {
            char nextCh = text.charAt(index);
            Map<Character, Node> childNodeMap = node.childNodeMap;
            if (childNodeMap != null) {
                if (childNodeMap.containsKey(nextCh)) {
                    Node nextNode = childNodeMap.get(nextCh);
                    if (nextNode.isWord) {
                        wordIndex = index;
                    }
                    return nextCh(text, index + 1, wordIndex, childNodeMap.get(nextCh));
                } else {
                    return wordIndex;
                }
            }
        }
        return wordIndex;
    }
}


三、前缀树使用及测试

1.向前缀树上增加词语

	String[] array = new String[]{"菜鸡","菜狗","张三","张三丰满", "张三丰", "张三丰是狗", "张全", "张力",  "张力懦夫","王五","王五王八", "王六滚蛋","滚蛋","滚"};
	TrieTree trieTree = new TrieTree();
	//将数组里面的所有词语加入到前缀树上
	trieTree.add(array);

2.根据输入匹配前缀树上的词语

一般像百度这样的搜索引擎会将用户经常搜索的文本加入到前缀树,这样用户只要在搜索框输入一两个字,搜索引擎就从前缀树上返回匹配到的相关前缀的词语,将其以下拉框的形式展示,方便用户选择。

	String[] array = new String[]{"菜鸡","菜狗","张三","张三丰满", "张三丰", "张三丰是狗", "张全", "张力",  "张力懦夫","王五","王五王八", "王六滚蛋","滚蛋","滚"};
	//将数组里面的所有词语加入到前缀树上
	trieTree.add(array);

	//匹配前缀树上 "张" 开头的词语有哪些
	List<String> matchResult = trieTree.matchStartWord("张");
	//输出:[张全, 张三, 张三丰, 张三丰满, 张三丰是狗, 张力, 张力懦夫]
	System.out.println(matchResult);

3.判断前缀树上是否有某个词语

		String[] array = new String[]{"菜鸡","菜狗","张三","张三丰满", "张三丰", "张三丰是狗", "张全", "张力",  "张力懦夫","王五","王五王八", "王六滚蛋","滚蛋","滚"};
		//将数组里面的所有词语加入到前缀树上
		trieTree.add(array);

		//判断前缀树上是否有 "张三" 这个词语
		boolean existWord = trieTree.haveWord("张三");
        System.out.println(existWord);  //true
        
		//判断前缀树上是否有 "张三四" 这个词语
        boolean existWord1 = trieTree.haveWord("张三四");
        System.out.println(existWord1); //false

4.判断输入的字符串是否是前缀树上某个词语的开头

		String[] array = new String[]{"菜鸡","菜狗","张三","张三丰满", "张三丰", "张三丰是狗", "张全", "张力",  "张力懦夫","王五","王五王八", "王六滚蛋","滚蛋","滚"};
		//将数组里面的所有词语加入到前缀树上
		trieTree.add(array);

		//判断前缀树上是否有 "王六滚" 开头的词语
        boolean haveStar = trieTree.startWordWith("王六滚");
        System.out.println(haveStar); //true

		//判断前缀树上是否有 "王六滚蛋" 开头的词语
        boolean haveStar = trieTree.startWordWith("王六滚蛋");
        System.out.println(haveStar); //true

5.从前缀树上删除某个词语

		String[] array = new String[]{"菜鸡","菜狗","张三","张三丰满", "张三丰", "张三丰是狗", "张全", "张力",  "张力懦夫","王五","王五王八", "王六滚蛋","滚蛋","滚"};
		//将数组里面的所有词语加入到前缀树上
		trieTree.add(array);


		System.out.println("没删除前前缀树含有"+trieTree.size()+"个词语");
        trieTree.deleteWord("张三");
        System.out.println("删除后前缀树含有"+trieTree.size()+"个词语");
        System.out.println("删除后前缀树是否含有词语 \"张三\" :"+trieTree.haveWord("张三"));

输出:

没删除前前缀树含有14个词语
删除后前缀树含有13个词语
前缀树是否含有词语 "张三"false

6.使用前缀树进行敏感词过滤

        String[] array = new String[]{"菜鸡","菜狗","张三","张三丰满", "张三丰", "张三丰是狗", "张全", "张力",  "张力懦夫","王五","王五王八", "王六滚蛋","滚蛋","滚"};
        trieTree.add(array);

         String text = "小张打游戏很厉害,但是张三打游戏真菜鸡。上次我在游戏里说张三菜狗他就生气了。他说滚,我就滚滚蛋了。\n"+
                "王五斗地主很厉害,上次王五王炸,张三丰就骂他,我们是队友你炸我干嘛,然后骂王五王八,王五听错了以为张三丰骂的,于是回骂张三丰是狗。\n"+
                "然后场面一度失控,旁边的张力也被波及,什么张力懦夫张力滚蛋王五王八王五滚蛋都被乱七八糟的骂!";
		//过滤后的文本
        String textFilter = trieTree.sensitiveWordReplace(text);
        System.out.println(textFilter);

输出:

小张打游戏很厉害,但是**打游戏真**。上次我在游戏里说****他就生气了。他说*,我就***了。
**斗地主很厉害,上次**王炸,***就骂他,我们是队友你炸我干嘛,然后骂******听错了以为***骂的,于是回骂*****。
然后场面一度失控,旁边的**也被波及,什么****************都被乱七八糟的骂!

7.使用前缀树对文章、消息进行敏感词标记

		String[] array = new String[]{"菜鸡","菜狗","张三","张三丰满", "张三丰", "张三丰是狗", "张全", "张力",  "张力懦夫","王五","王五王八", "王六滚蛋","滚蛋","滚"};
        trieTree.add(array);

         String text = "小张打游戏很厉害,但是张三打游戏真菜鸡。上次我在游戏里说张三菜狗他就生气了。他说滚,我就滚滚蛋了。\n"+
                "王五斗地主很厉害,上次王五王炸,张三丰就骂他,我们是队友你炸我干嘛,然后骂王五王八,王五听错了以为张三丰骂的,于是回骂张三丰是狗。\n"+
                "然后场面一度失控,旁边的张力也被波及,什么张力懦夫张力滚蛋王五王八王五滚蛋都被乱七八糟的骂!";
		//标记后的文本
        String mark = trieTree.markWord(text, "【", "】");
        System.out.println(mark);

输出:

小张打游戏很厉害,但是【张三】打游戏真【菜鸡】。上次我在游戏里说【张三】【菜狗】他就生气了。他说【滚】,我就【滚】【滚蛋】了。
【王五】斗地主很厉害,上次【王五】王炸,【张三丰】就骂他,我们是队友你炸我干嘛,然后骂【王五王八】,【王五】听错了以为【张三丰】骂的,于是回骂【张三丰是狗】。
然后场面一度失控,旁边的【张力】也被波及,什么【张力懦夫】【张力】【滚蛋】【王五王八】【王五】【滚蛋】都被乱七八糟的骂!

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

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

相关文章

Java 基于SpringBoot+Vue的社区医院管理系统的实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1、效果演示2、 前言介绍3. 技术栈4系统设计4.1数据库设计4.2系统整体设计4.2.1 系统设计思想4.2.…

加拿大CCPSA-SOR/2016-152(婴儿床、摇篮和婴儿摇篮法规)认证要求解答

亚马逊加拿大站儿童床垫CCPSA认证: ●SOR/2016-152&#xff08;婴儿床、摇篮和婴儿摇篮法规&#xff09; ●SOR/2018-83&#xff08;含铅消费品法规&#xff09; ●SOR/2016-193&#xff08;表面涂层材料法规 - 铅&#xff09; ●SOR/2016-188&#xff08;邻苯二甲酸盐&…

大漠插件普通定制版内存调用与com对象调用方法

首先.打开大漠类库生成工具.拖入定制版的dll文件会生成各个语言的调用例子 如下图所示 详见视频教程 大漠插件普通定制版内存调用与com对象调用方法

qt作业day5

//客户端&#xff0c;#include "tcpcli.h" #include "ui_tcpcli.h"TcpCli::TcpCli(QWidget *parent) :QWidget(parent),ui(new Ui::TcpCli) {ui->setupUi(this);//给客户端指针实例化对象cli_sock new QTcpSocket(this);ui->discntBtn->setEnabl…

Docker的运行机制和底层技术简介

&#xff08;笔记总结自《微服务架构基础》书籍&#xff09; 一、Docker的引擎 Docker Engine&#xff08;Docker引擎&#xff09;是Docker的核心部分&#xff0c;使用的是客户端-服务器&#xff08;C/S&#xff09;架构模式&#xff1a; ①Docker Cli&#xff1a;Docker命令…

金融工程学学习笔记第一章

第一章 金融工程概述 什么是金融工程 金融工程的含义 金融工程&#xff1a; 金融工程&#xff1a;一门融现代金融学、数理和工程方法与信息技术与一体的新兴交叉型学科。 工程是指以某种设想的目标为依据&#xff0c;应用有关科学知识和技术手段&#xff0c;通过有组织的一…

AI极客日报0908 - Zoom的AI新功能揭示;助听技术的未来趋势;ChatGPT与Canva的融合;机器人学习策略游戏

最新动态 &#x1f4bb; Zoom推出内置AI助手 概述&#xff1a;Zoom刚刚推出了AI Companion&#xff0c;这是一个嵌入其平台的AI助手&#xff0c;旨在提高生产效率 — 对付费用户来说没有额外费用。 关键点&#xff1a; AI Companion将协助用户撰写聊天回复&#xff0c;自动生…

preload和prefetch、dns-prefetch和preconnect

一、preload和prefetch 二、dns-prefetch和preconnect &#xff08;也是针对未来页面&#xff09; 三、总结

ModuleNotFoundError: No module named ‘lavis‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

idea所有历史版本下载

目录 链接直达 图文讲解 我idea嘎了&#xff0c;最新版的一直在闪退&#xff0c;于是我就在网上找idea的历史版本下载&#xff0c;结果都不太如意。 链接直达 idea历史版本 图文讲解 来到idea下载的官网 Download IntelliJ IDEA – The Leading Java and Kotlin IDE 一直…

面向过程的编程语言(如:C)和面向对象的编程语言(如:C++)的区别

面向过程的编程语言&#xff08;如C&#xff09;和面向对象的编程语言&#xff08;如C&#xff09;在编程范式上有明显的区别。 抽象级别: 面向过程的语言侧重于算法和过程的编写&#xff0c;将程序分解为一系列函数或过程的集合。而面向对象的语言则强调数据和对象的封装&…

python 创建 Telnet 客户端

目录 前言 1. Telnet 客户端框架 2. Telnet 代码分解 2.1 基于 TK 创建会话窗口 2.1.1 创建 Text 文本控件 2.1.2 创建 Frame 容器 2.1.2.1 基于 Frame 容器创建主机地址输入框 2.1.2.1.1 主机地址输入框绑定焦点事件 2.1.2.2 创建 Telnet 连接按钮控件 2.1.2.3 创建…

机器学习——图片处理应用(人脸补全)

0、前言&#xff1a;本文内容是机器学习算法回归预测的应用&#xff0c;其中数据集主要来自sklearn库自带的人脸数据&#xff0c;目的是通过KNN回归、线性回归、岭回归算法通过人物的左半张脸预测人物的右半张脸。 意义1&#xff1a;通过该项目掌握图片数据在机器学习当中的处…

CSS笔记(黑马程序员pink老师前端)圆角边框

圆角边框 border-radius:length; 效果显示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Documen…

【新项目】

01 概述 新凝血四项&#xff0c;又称血栓前四项&#xff0c;分为&#xff1a;TAT、PIC、TM、tPAI.C四个检测项目。 生理性高凝状态会伴随着孕妈妈们的整个妊娠期&#xff0c;凝血和纤溶系统的异常状态导致孕产妇发生静脉血栓栓塞症(VTE)的风险比一般人群高。非妊娠妇女凝血项…

LeetCode518. 零钱兑换 II

518. 零钱兑换 II 一、题目 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证…

RTMP流媒体服务器EasyDSS视频直播点播平台如何生成可自动播放的分享链接

EasyDSS支持一站式的上传、转码、直播、回放、嵌入、分享功能&#xff0c;具有多屏播放、自由组合、接口丰富等特点。平台可以为用户提供专业、稳定的直播推流、转码、分发和播放服务&#xff0c;全面满足超低延迟、超高画质、超大并发访问量的要求。在推流方面&#xff0c;Eas…

PostgreSQL PG15 新功能 PG_WALINSPECT

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis &#xff0c;Oracle ,Oceanbase 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加微信号 liuaustin3 &#xff08;…

ABAP内表排序

SORT在用于给内表排序时&#xff0c;后面可以用ASCENDING和DESCENDING进行升序和降序排列&#xff0c;但是这其中用法很多&#xff0c;经过尝试后总结如下&#xff1a; 1.SORT LT_TAB BY WERKS LGORT EMAIL. 正常排序并使用默认ASCENDING. 2.SORT LT_TAB BY WERKS LGORT EMAI…