SpringBoot過(guò)濾敏感詞的兩種實(shí)現(xiàn)方式
項(xiàng)目場(chǎng)景
基于 Spring Boot 的論壇系統(tǒng):用戶發(fā)布的內(nèi)容需進(jìn)行敏感詞過(guò)濾。
前置知識(shí)
前綴樹(shù)
- Q:前綴樹(shù)和普通N叉樹(shù)的區(qū)別?
- A:前綴樹(shù)(Trie,也稱字典樹(shù)、單詞查找樹(shù)或鍵樹(shù))。
- 設(shè)計(jì)目的:
- 前綴樹(shù):專門(mén)設(shè)計(jì)用于高效地存儲(chǔ)和檢索字符串集合中的鍵(字符串)。它的結(jié)構(gòu)允許快速的查找、插入和刪除操作,特別適合于字符串前綴匹配,如自動(dòng)補(bǔ)全、拼寫(xiě)檢查、敏感詞過(guò)濾等場(chǎng)景。
- 普通N叉樹(shù):是一種更通用的樹(shù)形數(shù)據(jù)結(jié)構(gòu),其中每個(gè)節(jié)點(diǎn)可以有任意數(shù)量(包括零)的子節(jié)點(diǎn),最多可達(dá)N個(gè)。它沒(méi)有特定于字符串處理的特性,廣泛應(yīng)用于各種需要多路分支的數(shù)據(jù)結(jié)構(gòu)場(chǎng)景,如文件系統(tǒng)的目錄結(jié)構(gòu)、表達(dá)式樹(shù)等。
- 節(jié)點(diǎn)結(jié)構(gòu):
- 前綴樹(shù):每個(gè)節(jié)點(diǎn)通常包含一個(gè)字符和一個(gè)映射到其子節(jié)點(diǎn)的字符到節(jié)點(diǎn)的映射(如HashMap或數(shù)組)。根節(jié)點(diǎn)通常不表示任何字符,而從根到任意葉節(jié)點(diǎn)的路徑上的字符序列組成一個(gè)字符串,葉節(jié)點(diǎn)或標(biāo)記為關(guān)鍵詞結(jié)束的節(jié)點(diǎn)代表一個(gè)完整的字符串。
- 普通N叉樹(shù):節(jié)點(diǎn)可能包含數(shù)據(jù)以及指向其子節(jié)點(diǎn)的指針數(shù)組或列表,但這些子節(jié)點(diǎn)之間的關(guān)系不一定有特定的字符關(guān)聯(lián),節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)和含義更多樣。
- 查找效率:
- 前綴樹(shù):由于其特殊的結(jié)構(gòu)設(shè)計(jì),前綴樹(shù)支持高效的前綴匹配,可以在O(L)時(shí)間內(nèi)(L為關(guān)鍵詞長(zhǎng)度)查找到所有具有相同前綴的字符串,或者確定某個(gè)字符串是否在集合中。
- 普通N叉樹(shù):查找效率依賴于樹(shù)的具體形態(tài)和查找算法,一般情況下不如前綴樹(shù)在字符串前綴匹配上的效率高。
- 空間利用率:
- 前綴樹(shù):可能會(huì)有較高的空間消耗,因?yàn)樗鎯?chǔ)了所有字符串的公共前綴,特別是當(dāng)存儲(chǔ)的字符串有很多相似前綴時(shí)。
- 普通N叉樹(shù):空間使用更加靈活,取決于樹(shù)的形狀,但通常不會(huì)為了存儲(chǔ)前綴信息而額外消耗空間。
- 設(shè)計(jì)目的:
總之,前綴樹(shù)是一種針對(duì)字符串處理優(yōu)化的特殊N叉樹(shù),強(qiáng)調(diào)了字符串前綴的高效存儲(chǔ)和查詢,而普通N叉樹(shù)則是一種更為通用的結(jié)構(gòu),適用于多種類(lèi)型的多路分支數(shù)據(jù)組織。
實(shí)現(xiàn)方式
解決方案一:讀取敏感詞文件生成前綴樹(shù)+構(gòu)建敏感詞過(guò)濾器
前綴樹(shù)
- 名稱:Trie 、字典樹(shù)、查找樹(shù)
- 特點(diǎn):查找效率高,消耗內(nèi)存大
- 應(yīng)用:字符串檢索、詞頻統(tǒng)計(jì)、字符串排序等
敏感詞過(guò)濾器
- 定義前綴樹(shù)
- 根據(jù)敏感詞,初始化前綴樹(shù)
- 編寫(xiě)過(guò)濾敏感詞的方法
1. 導(dǎo)入敏感詞文件 src/main/resources/sensitive_words.txt
此處為示例文件內(nèi)容。
元 購(gòu)物車(chē) fuck abc bf be
2. 構(gòu)建敏感詞過(guò)濾器 SensitiveFilter
package com.example.filter; import com.example.constant.SensitiveConstant; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; /** * 敏感詞過(guò)濾器 */ @Slf4j @Component public class SensitiveFilter { // 創(chuàng)建前綴樹(shù)的根節(jié)點(diǎn) private final TrieNode rootNode = new TrieNode(); /** * 初始化,讀取敏感詞 */ @PostConstruct // 確保在Bean初始化后執(zhí)行 public void init() { // 1. 從類(lèi)路徑加載敏感詞文件: 使用文件存儲(chǔ)敏感詞庫(kù)可以直接進(jìn)行一次性讀取和處理,無(wú)需依賴數(shù)據(jù)庫(kù)系統(tǒng),并且保證離線可用。 try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(SensitiveConstant.SENSITIVE_WORDS_FILE); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); // Java 7及以后的版本中,引入了一項(xiàng)稱為"try-with-resources"的新特性,它允許自動(dòng)管理資源,確保在try語(yǔ)句塊執(zhí)行完畢后,不論是否發(fā)生異常,都會(huì)正確關(guān)閉或釋放資源 ) { // 2. 逐行讀取敏感詞并加入到前綴樹(shù)中 String keyword; while ((keyword = bufferedReader.readLine()) != null) { this.addKeyWord(keyword); } } catch (IOException ex) { log.error("敏感詞文件讀取失敗: " + ex.getMessage()); } } /** * 將敏感詞添加到前綴樹(shù)中 * @param keyword 待添加的敏感詞 */ private void addKeyWord(String keyword) { // 1. 初始化操作,將當(dāng)前處理節(jié)點(diǎn)設(shè)為根節(jié)點(diǎn) TrieNode tempNode = rootNode; // 2. 將待添加的敏感詞字符串轉(zhuǎn)換為字符數(shù)組以便遍歷 char[] chars = keyword.toCharArray(); // 3. 遍歷字符數(shù)組,逐個(gè)字符構(gòu)建前綴樹(shù) for (int i = 0; i < chars.length; i++) { // 3.1. 從當(dāng)前處理節(jié)點(diǎn)的子節(jié)點(diǎn)Map中獲取當(dāng)前字符對(duì)應(yīng)的節(jié)點(diǎn) TrieNode childrenNode = tempNode.getChildrenNode(chars[i]); // 3.1.1 若當(dāng)前字符沒(méi)有對(duì)應(yīng)的節(jié)點(diǎn),則創(chuàng)建一個(gè)新節(jié)點(diǎn),并放入當(dāng)前處理節(jié)點(diǎn)的子節(jié)點(diǎn)Map中 if (childrenNode == null) { childrenNode = new TrieNode(); tempNode.setChildrenNode(chars[i], childrenNode); } // 3.2. 若當(dāng)前字符為敏感詞字符串的最后一個(gè)字符,則標(biāo)記當(dāng)前字符的對(duì)應(yīng)節(jié)點(diǎn)為敏感詞的結(jié)尾節(jié)點(diǎn) if (i == chars.length - 1) { childrenNode.setKeywordEnd(true); } // 3.3. 移動(dòng)到下一層,使當(dāng)前字符對(duì)應(yīng)的節(jié)點(diǎn)成為新的當(dāng)前處理節(jié)點(diǎn),繼續(xù)構(gòu)建或遍歷過(guò)程 tempNode = childrenNode; } } /** * 過(guò)濾文本,移除或替換其中的敏感詞,并返回處理后的文本 * @param text 待過(guò)濾的文本 * @return 過(guò)濾后的文本 */ public String filter(String text) { // 1. 檢查輸入文本是否為空或僅包含空白字符,如果是,則直接返回null,表示無(wú)內(nèi)容無(wú)需過(guò)濾 if (StringUtils.isBlank(text)) { return null; } // 2. 初始化變量 TrieNode tempNode = rootNode; // 2.1. 初始化為前綴樹(shù)的根節(jié)點(diǎn),用于遍歷查找。 int begin = 0, end = 0; // 2.2. 分別用于標(biāo)記待檢查文本區(qū)間的起始和結(jié)束位置 StringBuilder result = new StringBuilder(); // 2.3. 累積過(guò)濾后的文本 // 3. 遍歷整個(gè)文本進(jìn)行過(guò)濾處理 while (begin < text.length()) { char c = text.charAt(end); // 3.1. 遇到符號(hào)字符 if (isSymbol(c)) { // 3.1.1. 如果當(dāng)前處于根節(jié)點(diǎn),即沒(méi)有匹配到任何敏感詞的開(kāi)始,直接保留符號(hào),并移動(dòng)begin if (tempNode == rootNode) { result.append(c); begin++; } // 3.1.2. 跳過(guò)當(dāng)前符號(hào),并繼續(xù)檢查下一個(gè)字符 end++; continue; } // 3.2. 檢查當(dāng)前字符區(qū)間text[begin...end]是否是某個(gè)敏感詞的一部分 tempNode = tempNode.getChildrenNode(c); // 3.2.1. 如果當(dāng)前字符沒(méi)有對(duì)應(yīng)的子節(jié)點(diǎn),即text[begin...end]不是敏感詞的組成部分 if (tempNode == null) { result.append(text.charAt(begin)); // 3.2.1.1. 保存begin位置的字符到結(jié)果 end = ++begin; // 3.2.1.2. 移動(dòng)begin和end指針,準(zhǔn)備檢查下一個(gè)可能的敏感詞 begin++; end = begin; tempNode = rootNode; // 3.2.1.3. 重置tempNode為根節(jié)點(diǎn)準(zhǔn)備下一輪匹配 } // 3.2.2. 如果當(dāng)前字符路徑已到達(dá)一個(gè)敏感詞的結(jié)尾,即text[begin...end]是敏感詞 else if (tempNode.isKeywordEnd()) { result.append(StringUtils.repeat(SensitiveConstant.REPLACEMENT, end - begin + 1)); // 3.2.2.1. 替換敏感詞區(qū)間 begin = ++end; // 3.2.2.2. 移動(dòng)begin和end指針,準(zhǔn)備檢查下一個(gè)可能的敏感詞 end++; begin = end; tempNode = rootNode; // 3.2.2.3. 重置tempNode為根節(jié)點(diǎn)準(zhǔn)備下一輪匹配 } // 3.2.3. 如果當(dāng)前字符區(qū)間text[begin...end]是潛在敏感詞的一部分但不是結(jié)尾,繼續(xù)匹配下一個(gè)字符 else { // 3.2.3.1. 檢查下一個(gè)字符是否存在,存在則移動(dòng)end指針;否則,回退begin到當(dāng)前位置,準(zhǔn)備重新匹配 if (end < text.length() - 1) { end++; } else { end = begin; } } } // 4. 將剩余未檢查的文本(若有)添加到結(jié)果中 result.append(text.substring(begin)); // 5. 返回過(guò)濾后的文本 return result.toString(); } /** * 判斷字符是否為符號(hào)字符 * @param character 待判斷的字符 * @return true表示是符號(hào)字符,需要跳過(guò);false表示不是符號(hào)字符,不跳過(guò) */ private boolean isSymbol(Character character) { // 0x2E80~0x9FFF是東亞文字: 判斷字符是否不屬于ASCII字母數(shù)字且不在東亞文字范圍內(nèi) return !CharUtils.isAsciiAlphanumeric(character) && (character < 0x2E80 || character > 0x9FFF); } /** * 內(nèi)部類(lèi),定義前綴樹(shù)的節(jié)點(diǎn) */ private static class TrieNode { private boolean isKeywordEnd = false; // 當(dāng)前節(jié)點(diǎn)是否為敏感詞的末尾節(jié)點(diǎn) private final Map<Character, TrieNode> childrenNode = new HashMap<>(); // 當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn) // Getter和Setter方法 public boolean isKeywordEnd() { return isKeywordEnd; } public void setKeywordEnd(boolean keywordEnd) { isKeywordEnd = keywordEnd; } public TrieNode getChildrenNode(Character c) { return childrenNode.get(c); } public void setChildrenNode(Character c, TrieNode node) { childrenNode.put(c, node); } } }
這里我把敏感詞過(guò)濾器的相關(guān)常量都寫(xiě)到一個(gè)常量類(lèi)里了,也可以直接寫(xiě)在過(guò)濾器里。
package com.example.constant; /** * 敏感詞過(guò)濾器的相關(guān)常量 */ public class SensitiveConstant { /** * 敏感詞文件名稱 */ public static final String SENSITIVE_WORDS_FILE = "sensitive_words.txt"; /** * 用于替換敏感詞的字符 */ public static final Character REPLACEMENT = '*'; }
3. 測(cè)試與使用
創(chuàng)建一個(gè)測(cè)試類(lèi)測(cè)一下就行。
package com.example.filter; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class SensitiveFilterTest { @Autowired private SensitiveFilter sensitiveFilter; @Test void filter() { String result = sensitiveFilter.filter("accfuckxx元bitch購(gòu)物車(chē)bitchjwbc"); System.out.println(result); } }
運(yùn)行結(jié)果:
acc****xx*bitch***bitchjwbc
可以實(shí)現(xiàn)過(guò)濾,但是完全基于敏感詞文件內(nèi)容,需要自行完善文件。也許可以結(jié)合正則表達(dá)式擴(kuò)展?不是很靈活,沒(méi)研究過(guò)了。
解決方案二:使用第三方插件 houbb/sensitive-word(推薦)
houbb/sensitive-word
項(xiàng)目源碼:https://github.com/houbb/sensitive-word
1. 添加依賴
<dependency> <groupId>com.github.houbb</groupId> <artifactId>sensitive-word</artifactId> <version>0.17.0</version> </dependency>
2. 測(cè)試與使用(使用默認(rèn)過(guò)濾策略)
package com.example.filter; import com.github.houbb.sensitive.word.core.SensitiveWordHelper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class SensitiveFilterTest { @Autowired private SensitiveFilter sensitiveFilter; @Test void filter() { String result = sensitiveFilter.filter("accfuckxx元bitch購(gòu)物車(chē)bitchjwbc"); System.out.println(result); System.out.println("-------------------------------"); result = SensitiveWordHelper.replace("accfuckxx元bitch購(gòu)物車(chē)bitchjwbc"); System.out.println(result); result = SensitiveWordHelper.replace("??c? the bad words"); System.out.println(result); result = SensitiveWordHelper.replace("???f?u??c?? the bad words"); System.out.println(result); result = SensitiveWordHelper.replace("fffuuck the bad words"); System.out.println(result); } }
運(yùn)行結(jié)果:
acc****xx*bitch***bitchjwbc
-------------------------------
acc****xx元bitch購(gòu)物車(chē)bitchjwbc
**** the bad words
???f?u??c?? the bad words
fffuuck the bad words
可以實(shí)現(xiàn)基礎(chǔ)過(guò)濾。比讀取文件更方便。
(忽略掉這里的“元”和“購(gòu)物車(chē)”。只是為了文章過(guò)審把一些很offensive的詞換成了隨便寫(xiě)的詞嗯。)
如果要設(shè)置更多需要過(guò)濾的內(nèi)容,可以參考以下步驟。
3. 構(gòu)建配置類(lèi)SensitiveWordConfig
(使用自定義過(guò)濾策略)
自己按照需求調(diào)一下就好。
package com.example.config; import com.github.houbb.sensitive.word.bs.SensitiveWordBs; import com.github.houbb.sensitive.word.support.ignore.SensitiveWordCharIgnores; import com.github.houbb.sensitive.word.support.resultcondition.WordResultConditions; import com.github.houbb.sensitive.word.support.tag.WordTags; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 配置類(lèi),用于設(shè)置敏感詞過(guò)濾器的自定義過(guò)濾策略 * 更多配置見(jiàn) https://github.com/houbb/sensitive-word */ @Configuration public class SensitiveWordConfig { /** * 初始化引導(dǎo)類(lèi) * @return 初始化引導(dǎo)類(lèi) * @since 1.0.0 */ @Bean public SensitiveWordBs sensitiveWordBs() { SensitiveWordBs wordBs = SensitiveWordBs.newInstance() .ignoreCase(true) // 忽略大小寫(xiě),默認(rèn)值為true .ignoreWidth(true) // 忽略半角圓角,默認(rèn)值為true .ignoreNumStyle(true) // 忽略數(shù)字的寫(xiě)法,默認(rèn)值為true .ignoreChineseStyle(true) // 忽略中文的書(shū)寫(xiě)格式,默認(rèn)值為true .ignoreEnglishStyle(true) // 忽略英文的書(shū)寫(xiě)格式,默認(rèn)值為true .ignoreRepeat(false) // 忽略重復(fù)詞,默認(rèn)值為false .enableNumCheck(false) // 是否啟用數(shù)字檢測(cè),默認(rèn)值為false .enableEmailCheck(false) // 是有啟用郵箱檢測(cè),默認(rèn)值為false .enableUrlCheck(false) // 是否啟用鏈接檢測(cè),默認(rèn)值為false .enableIpv4Check(false) // 是否啟用IPv4檢測(cè),默認(rèn)值為false .enableWordCheck(true) // 是否啟用敏感單詞檢測(cè),默認(rèn)值為true .numCheckLen(8) // 數(shù)字檢測(cè),自定義指定長(zhǎng)度,默認(rèn)值為8 .wordTag(WordTags.none()) // 詞對(duì)應(yīng)的標(biāo)簽,默認(rèn)值為none .charIgnore(SensitiveWordCharIgnores.defaults()) // 忽略的字符,默認(rèn)值為none .wordResultCondition(WordResultConditions.alwaysTrue()) // 針對(duì)匹配的敏感詞額外加工,比如可以限制英文單詞必須全匹配,默認(rèn)恒為真 .init(); return wordBs; } }
4. 測(cè)試與使用(使用自定義過(guò)濾策略)
在剛剛的配置類(lèi)中已經(jīng)啟用了數(shù)字檢測(cè)。
.enableNumCheck(true) // 是否啟用數(shù)字檢測(cè),默認(rèn)值為false
測(cè)一下。
package com.example.filter; import com.github.houbb.sensitive.word.bs.SensitiveWordBs; import com.github.houbb.sensitive.word.core.SensitiveWordHelper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class SensitiveFilterTest { ... @Autowired private SensitiveWordBs sensitiveWordBs; ... @Test void test() { String result = sensitiveWordBs.replace("accfuckxx元bitch購(gòu)物車(chē)bitchjwbc"); System.out.println(result); result = sensitiveWordBs.replace("??c? the bad words"); System.out.println(result); result = sensitiveWordBs.replace("???f?u??c?? the bad words"); System.out.println(result); result = sensitiveWordBs.replace("fffuuck the bad words"); System.out.println(result); result = sensitiveWordBs.replace("12345678dwnoxcw"); System.out.println(result); result = sensitiveWordBs.replace("123456789dwnoxcw"); System.out.println(result); result = sensitiveWordBs.replace("一二三四五六七八九dwnoxcw"); System.out.println(result); result = sensitiveWordBs.replace("這個(gè)是我的微信:9?二肆??③⑸⒋?㈤五"); System.out.println(result); } }
運(yùn)行結(jié)果:
acc****xx元bitch購(gòu)物車(chē)bitchjwbc
**** the bad words
*********** the bad words
******* the bad words
********dwnoxcw
*********dwnoxcw
*********dwnoxcw
這個(gè)是我的微信:************
就很好用了。
(忽略掉這里的“元”和“購(gòu)物車(chē)”。只是為了文章過(guò)審把一些很offensive的詞換成了隨便寫(xiě)的詞嗯。)
兩種實(shí)現(xiàn)方式的對(duì)比
- 自行構(gòu)建前綴樹(shù)過(guò)濾器:
- 優(yōu)勢(shì):高度定制,易于理解與維護(hù),無(wú)外部依賴。
- 劣勢(shì):開(kāi)發(fā)耗時(shí),需優(yōu)化性能,學(xué)習(xí)成本。
- 使用第三方開(kāi)源項(xiàng)目:
- 優(yōu)勢(shì):快速集成,功能成熟,社區(qū)支持。
- 劣勢(shì):依賴管理,安全風(fēng)險(xiǎn),定制受限。
根據(jù)項(xiàng)目需求緊迫性、定制化需求及團(tuán)隊(duì)技術(shù)背景綜合選擇。簡(jiǎn)單需求或有定制化要求傾向自建;追求快速、功能全面則推薦使用現(xiàn)成開(kāi)源方案。
以上就是SpringBoot過(guò)濾敏感詞的兩種實(shí)現(xiàn)方式的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot過(guò)濾敏感詞的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Javaweb實(shí)戰(zhàn)之實(shí)現(xiàn)蛋糕訂購(gòu)系統(tǒng)
隨著網(wǎng)絡(luò)的普及與發(fā)展,網(wǎng)上購(gòu)物逐漸成為一種主流消費(fèi)的方式。這篇文章主要介紹了通過(guò)JavaWeb制作一個(gè)線上蛋糕訂購(gòu)系統(tǒng),文中示例代碼講解詳細(xì),需要的朋友可以參考一下2021-12-12Java同步鎖synchronized用法的最全總結(jié)
這篇文章主要介紹了Java同步鎖synchronized用法的最全總結(jié),需要的朋友可以參考下,文章詳細(xì)講解了Java同步鎖Synchronized的使用方法和需要注意的點(diǎn),希望對(duì)你有所幫助2023-03-03Java讀取Excel、docx、pdf和txt等文件萬(wàn)能方法舉例
在Java開(kāi)發(fā)中處理文件是常見(jiàn)需求,本文以實(shí)際代碼示例詳述如何使用ApachePOI庫(kù)及其他工具讀取和寫(xiě)入Excel、Word、PDF等文件,介紹了ApachePOI、ApachePDFBox和EasyExcel等庫(kù)的使用方法,幫助開(kāi)發(fā)者有效讀取不同格式文件,需要的朋友可以參考下2024-09-09springboot jpaRepository為何一定要對(duì)Entity序列化
這篇文章主要介紹了springboot jpaRepository為何一定要對(duì)Entity序列化,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12SpringBoot+Mybatis-plus+shardingsphere實(shí)現(xiàn)分庫(kù)分表的方案
實(shí)現(xiàn)億級(jí)數(shù)據(jù)量分庫(kù)分表的項(xiàng)目是一個(gè)挑戰(zhàn)性很高的任務(wù),下面是一個(gè)基于Spring Boot的簡(jiǎn)單實(shí)現(xiàn)方案,感興趣的朋友一起看看吧2024-03-03Java用split分割含一個(gè)或多個(gè)空格的字符串案例
這篇文章主要介紹了Java用split分割含一個(gè)或多個(gè)空格的字符串案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)過(guò)來(lái)看看吧2020-09-09Java結(jié)合Swing實(shí)現(xiàn)龍年祝福語(yǔ)生成工具
Swing是一個(gè)為Java設(shè)計(jì)的GUI工具包,屬于Java基礎(chǔ)類(lèi)的一部分,本文將使用Java和Swing實(shí)現(xiàn)龍年祝福語(yǔ)生成工具,感興趣的小伙伴可以了解下2024-01-01