牛客高级项目课(5)

tech2024-08-07  56

一、问题发布二、HTML/敏感词过滤三、多线程

一、问题发布

1.新建QuestionController 2.使用questionService,在其中添加addQuestion方法

public int addQuestion(Question question){ //敏感词过滤 return questionDAO.addQuestion(question)>0?question.getId():0; }

3.新建QuestionDAO

@Mapper public interface QuestionDAO { String TABLE_NAME = " question "; String INSERT_FIELDS = " title, content, created_date, user_id, comment_count "; String SELECT_FIELDS = " id, " + INSERT_FIELDS; @Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS, ") values (#{title},#{content},#{createdDate},#{userId},#{commentCount})"}) int addQuestion(Question question); List<Question> selectLatestQuestions(@Param("userId") int userId, @Param("offset") int offset, @Param("limit") int limit); }

4.QuestionController 中添加addQuestion方法,参数为题目名+内容

@Controller public class QuestionController { private static final Logger logger = LoggerFactory.getLogger(QuestionController.class); @Autowired QuestionService questionService; @Autowired HostHolder hostHolder; @RequestMapping(value="/question/add",method ={RequestMethod.POST}) @ResponseBody public String addQuestion(@RequestParam("title") String title,@RequestParam ("content") String content){ try { Question question=new Question(); question.setContent(content); question.setTitle(title); question.setCommentCount(0); question.setCreatedDate(new Date()); if (hostHolder.getUser()==null){ question.setUserId(WendaUtil.ANONYMOUS_USERID);//匿名用户id }else { question.setUserId(hostHolder.getUser().getId()); } if(questionService.addQuestion(question)>0){ return WendaUtil.getJSONString(0); } }catch (Exception e){ logger.error("增加题目失败"+e.getMessage()); } return WendaUtil.getJSONString(1,"失败"); } }

二、HTML/敏感词过滤

1.HTML标签过滤 将QuestionService中的addQuestion加入HtmlUtils.htmlEscape 将html标签都过滤掉

public int addQuestion(Question question){ //敏感词过滤 question.setContent(HtmlUtils.htmlEscape(question.getContent()));//将html标签都过滤掉 question.setTitle(HtmlUtils.htmlEscape(question.getTitle())); return questionDAO.addQuestion(question)>0?question.getId():0; }

2.敏感词过滤 方法:前缀树

【前缀树】 Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。 Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。 它有3个基本性质: (1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。 (2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。 (3)每个节点的所有子节点包含的字符都不相同。

使用举例: 1)给定敏感词 abc,bf, be可以构建出以下的前缀树: 2)利用前缀树进行敏感词过滤 假设现在有一个字符串xwabfabcff,使用前面的前缀树,那么过滤过程如下:

首先定义三个指针,指针1指向前缀树的根节点,指针2指向需要判断的字符开头,指针3指向当前需要判断的字符。对于前缀树中的每个叶子节点打上标记,表示敏感词的结束。 接着判断指针2和指针3之间的字符是否属于敏感词。 假设指针2,3指向x,由于Root的子节点不存在x,那么指针 3++,指针2++,接着往后进行判断。w同理。 当指针2走到a的时候,由于Root的子节点中存在a,那么指针1指向字节点a,指针2不动,指针3往后走一步,指针3指向b,由于b是a的子节点,那么指针1指向节点b,指针3继续往后走,然后发现f不是节点b的子节点,所以指针2~指针3之间的字符不是敏感词,指针2++,指针3=指针2,继续往后判断。 指针3指向了b,但b不是敏感词的结束,指针3++。然后按照上述流程,接着判断f,在判断f的时候,发现f上面有敏感词结束标识,说明指针2到指针3之间的bf是敏感词,对bf进行替换,然后指针2++继续往后判断,指针2 =++指针3,直接走到a接着判断。剩余同理。 参考文章

代码实现: 存储敏感词,可以用文件存储,也可以使用数据库,此处使用文件进行存储了。 在resources里新建SensitiveWords.txt,里面输入敏感词

package com.nowcoder.service; import com.nowcoder.controller.HomeController; import javafx.fxml.Initializable; import org.apache.commons.lang.CharUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Service; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; @Service public class SensitiveService implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(SensitiveService.class); /** * 初始化前缀树 */ @Override public void afterPropertiesSet() throws Exception { try{ InputStream is=Thread.currentThread().getContextClassLoader().getResourceAsStream("SensitiveWords.txt"); InputStreamReader read=new InputStreamReader(is); BufferedReader bufferedReader=new BufferedReader(read); String lineTxt; while ((lineTxt=bufferedReader.readLine())!=null){ addWord(lineTxt.trim()); } read.close(); }catch (Exception e){ logger.error("读取敏感词文件失败"); } } /** * 将一个敏感词加入到前缀树中 * @param lineTxt */ private void addWord(String lineTxt) { TrieNode tempNode = rootNode; // 循环每个字节 for (int i = 0; i < lineTxt.length(); ++i) { Character c = lineTxt.charAt(i); // 过滤空格 if (isSymbol(c)) { continue; } TrieNode node = tempNode.getSubNode(c); if (node == null) { // 没初始化 node = new TrieNode(); tempNode.addSubNode(c, node); } tempNode = node; if (i == lineTxt.length() - 1) { // 关键词结束, 设置结束标志 tempNode.setKeywordEnd(true); } } } private class TrieNode{ private boolean end=false;//是否为关键词结尾 private Map<Character,TrieNode> subNodes=new HashMap<>();//当前节点下的所有子节点 public void addSubNode(Character key,TrieNode node){ subNodes.put(key,node); } TrieNode getSubNode(Character key){ return subNodes.get(key); } boolean isKeyWordEnd(){ return end; } void setKeywordEnd(boolean end){ this.end=end; } } private TrieNode rootNode=new TrieNode(); /** * 对文本中的敏感词进行过滤 * @param text 待过滤的敏感词 * @return 过滤之后的敏感词 */ public String filter(String text){ if (StringUtils.isBlank(text)){//如果text为空 return text; } StringBuilder result=new StringBuilder(); String replacement="***"; TrieNode tempNode=rootNode;//指针1 int begin=0; int position=0; while (position<text.length()){ char c=text.charAt(position); if(isSymbol(c)){ if(tempNode==rootNode){ result.append(c); } ++position; continue; } tempNode=tempNode.getSubNode(c); //检查下级节点 if(tempNode==null){ result.append(text.charAt(begin));//以begin开头的字符串不是敏感词 position=begin+1; //进入下一个位置 begin=position; tempNode=rootNode; //重新指向根节点 }else if(tempNode.isKeyWordEnd()){//发现敏感词,将begin~position字符串替换掉 result.append(replacement); position=position+1; begin=position; tempNode=rootNode; }else{ //检查下一个字符 position++; } } result.append(text.substring(begin)); //将最后一批字符计入结果 return result.toString(); } /** * 判断该字符是否是符号 * @param c * @return */ private boolean isSymbol(char c){ int ic=(int)c; return !CharUtils.isAsciiAlphanumeric(c)&&(ic<0x2E80||ic>0x9FFF); //0x2E80~0x9FFF 是东亚文字范围 } public static void main(String[] args) { SensitiveService s=new SensitiveService(); s.addWord("色情"); s.addWord("赌博"); System.out.println(s.filter("你好 赌 博")); } }

在QuestionService中添加敏感词过滤的两行

public int addQuestion(Question question) throws UnsupportedEncodingException { question.setContent(HtmlUtils.htmlEscape(question.getContent()));//将html标签都过滤掉 question.setTitle(HtmlUtils.htmlEscape(question.getTitle())); //敏感词过滤 question.setContent(sensitiveService.filter(question.getContent())); question.setTitle(sensitiveService.filter(question.getTitle())); return questionDAO.addQuestion(question)>0?question.getId():0; }

点击问题,展开详情 首先在QuestionDAO加入

@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"}) Question selectById(int id);

QuestionService加入

@Autowired SensitiveService sensitiveService; public Question selectById(int id){ return questionDAO.selectById(id); }

QuestionController加入

@RequestMapping(value = "/question/{qid}") public String questionDetail(Model model, @PathVariable("qid") int qid){ Question question=questionService.selectById(qid); model.addAttribute("question",question); model.addAttribute("user",userService.getUser(question.getUserId())); return "detail"; }

加入detail.html,略。

三、多线程

1.启动线程的方法: 1).extends Thread.重载run()方法

package com.nowcoder; class MyThread extends Thread{ private int pid; public MyThread(int pid) { this.pid = pid; } @Override public void run() { try { for(int i=0;i<10;i++){ Thread.sleep(1000); System.out.println(String.format("%d:%d",pid,i)); } }catch (Exception e){ e.printStackTrace(); } } } public class MultiThreadTests { public static void testThread(){ for(int i=0;i<10;i++){ new MyThread(i).start();//启动10个线程 } } public static void main(String[] args) { testThread(); } }

2):implement Runnable(),实现run()方法

package com.nowcoder; public class MultiThreadTests { public static void testThread(){ for(int i=0;i<10;i++){ new Thread(new Runnable() { @Override public void run() { try { for(int j=0;j<10;j++){ Thread.sleep(1000); System.out.println(String.format("%d",)); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } } public static void main(String[] args) { testThread(); } }

2.Synchronized-内置锁 1). 放在方法上会锁住所有synchronized方法 2). synchronized(obj) 锁住相关的代码段

public static void testSynchronized1() { synchronized (obj) { Random random = new Random(); for (int i = 0; i < 10; ++i) { sleep(random.nextInt(1000)); } } }

3.BlockingQueue 同步队列

class Consumer implements Runnable { private BlockingQueue<String> q; public Consumer(BlockingQueue<String> q) { this.q = q; } @Override public void run() { try { while (true) { System.out.println(Thread.currentThread().getName() + ":" + q.take()); } } catch (Exception e) { e.printStackTrace(); } } } class Producer implements Runnable { private BlockingQueue<String> q; public Producer(BlockingQueue<String> q) { this.q = q; } @Override public void run() { try { for (int i = 0; i < 100; ++i) { Thread.sleep(1000); q.put(String .valueOf(i)); } } catch (Exception e) { e.printStackTrace(); } } } public static void testBlockingQueue() { BlockingQueue<String> q = new ArrayBlockingQueue<String>(10); new Thread(new Producer(q)).start(); new Thread(new Consumer(q), "Consumer1").start(); new Thread(new Consumer(q), "Consumer2").start(); }

4.ThreadLocal 1). 线程局部变量。即使是一个static成员,每个线程访 问的变量是不同的。 2). 常见于web中存储当前用户到一个静态工具类中,在 线程的任何地方都可以访问到当前线程的用户。 3). 参考HostHolder.java里的users

private static ThreadLocal<Integer> threadLocalUserIds = new ThreadLocal<>(); private static int userId; public static void testThreadLocal() { for (int i = 0; i < 10; ++i) { final int finalI = i; new Thread(new Runnable() { @Override public void run() { try { threadLocalUserIds.set(finalI); Thread.sleep(1000); System.out.println("ThreadLocal:" + threadLocalUserIds.get()); } catch (Exception e) { e.printStackTrace(); } } }).start(); } for (int i = 0; i < 10; ++i) { final int finalI = i; new Thread(new Runnable() { @Override public void run() { try { userId = finalI; Thread.sleep(1000); System.out.println("UserId:" + userId); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }

5.Executor 1.) 提供一个运行任务的框架。 2). 将任务和如何运行任务解耦。 3). 常用于提供线程池或定时任务服务

public static void testExecutor() { //ExecutorService service = Executors.newSingleThreadExecutor(); ExecutorService service = Executors.newFixedThreadPool(2); service.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 10; ++i) { try { Thread.sleep(1000); System.out.println("Executor1:" + i); } catch (Exception e) { e.printStackTrace(); } } } }); service.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 10; ++i) { try { Thread.sleep(1000); System.out.println("Executor2:" + i); } catch (Exception e) { e.printStackTrace(); } } } }); service.shutdown(); while (!service.isTerminated()) { try { Thread.sleep(1000); System.out.println("Wait for termination."); } catch (Exception e) { e.printStackTrace(); } } }

6Future

public static void testFuture() { ExecutorService service = Executors.newSingleThreadExecutor(); Future<Integer> future = service.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { //Thread.sleep(1000); throw new IllegalArgumentException("异常"); //return 1; } }); service.shutdown(); try { System.out.println(future.get()); //System.out.println(future.get(100, TimeUnit.MILLISECONDS)); } catch (Exception e) { e.printStackTrace(); } } 返回异步结果阻塞等待返回结果timeout获取线程中的Exception
最新回复(0)