一、拦截器与过滤器的区别

  1. 过滤器 (Filter)
    过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。
  • init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。
  • doFilter() :容器中的每一次请求都会调用该方法FilterChain 用来调用下一个过滤器 Filter
  • destroy(): 当容器销毁过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
  1. 拦截器 (Interceptor)
    拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行。首先编写一个简单的拦截器处理类,请求的拦截是通过HandlerInterceptor 来实现,看到HandlerInterceptor 接口中也定义了三个方法。
  • preHandle() :这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
  • postHandle():只有在 preHandle()方法返回值为true 时才会执行会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。有意思的是:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
  • afterCompletion():只有在 preHandle()方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。
  1. 过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
    如下图:
  2. 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。

过滤器拦截器运行先后步骤:

其中第2步,SpringMVC的机制是由DispaterServlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的

  1. 过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射,代理分静态代理和动态代理,动态代理是拦截器的简单实现。
  2. 拦截器加载的时间点在springcontext之前。
  3. 过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用
  4. 过滤器 和 拦截器 均体现了AOP的编程思想,都可以实现诸如日志记录、登录鉴权等功能

二、何时使用拦截器?何时使用过滤器?何时使用监听器

  1. 如果是非spring项目,那么拦截器不能用,只能使用过滤器。
  2. 如果是处理controller前后,既可以使用拦截器也可以使用过滤器。
  3. 如果是处理dispaterServlet前后,只能使用过滤器。
  4. 监听 Servlet 上下文用来初始化一些数据、监听 HTTP Session 用来获取当前在线的人数、监听客户端请求的 ServletRequest 对象来获取用户的访问信息等等,使用监听器。
  • 具体使用场景
  1. 过滤器:可以对请求的URL进行过滤, 对敏感词过滤,
  2. 拦截器:性能分析, 权限检查, 日志记录

灵活性上说拦截器功能更强大些,Filter能做的事情,他都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。

三、spring boot如何使用过滤器、拦截器

  1. Spring boot过滤器的使用(两种方式)
  1. 使用spring boot提供的FilterRegistrationBean注册Filter
  2. 使用原生servlet注解定义Filter

两种方式的本质都是一样的,都是去FilterRegistrationBean注册自定义Filter

  • 方式一:

①、先定义Filter:

import javax.servlet.*;
import java.io.IOException;
public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// do something 处理request 或responseSystem.out.println("filter1");// 调用filter链中的下一个filterfilterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {}
}

②、注册自定义Filter

@Configuration
public class FilterConfig {@Beanpublic FilterRegistrationBean registrationBean() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());filterRegistrationBean.addUrlPatterns("/*");return filterRegistrationBean;}
}
  • 方式二:
// 注入spring容器
@Component
// 定义filterName 和过滤的url
@WebFilter(filterName = "my2Filter" ,urlPatterns = "/*")
public class My2Filter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("filter2");}@Overridepublic void destroy() {}
}
  1. Spring boot拦截器的使用:

①、定义拦截器:

public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {System.out.println("postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {System.out.println("afterCompletion");}
}

②、配置拦截器:

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyInterceptor());}
}

③Controller演示:

@RestController
public class UController {@GetMapping("/home")public String home(){System.out.println("home");return "myhome";}
}

四、过滤器拦截器完整例子

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyInterceptor());registry.addInterceptor(new MyInterceptor2());}
}
@Order(1)
@Component
@WebFilter(filterName = "myFilter",urlPatterns = "/*")
public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("MyFilter1-----过滤器的init()方法,随着容器的启动进行了初始化");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// do something 处理request 或responseSystem.out.println("filter1");// 调用filter链中的下一个filterfilterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {System.out.println("MyFilter1--------过滤器的destroy()方法,随着容器的关闭而进行");}
}
@Order(2)
@Component
@WebFilter(filterName = "myFilter2",urlPatterns = "/*")
public class MyFilter2 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("MyFilter2------过滤器的init()方法,随着容器的启动进行了初始化");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// do something 处理request 或responseSystem.out.println("filter2");// 调用filter链中的下一个filterfilterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {System.out.println("MyFilter2-----过滤器的destroy()方法,随着容器的关闭而进行");}
}
@Order(3)
@Component
@WebFilter(filterName = "myFilter3",urlPatterns = "/*")
public class MyFilter3  implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("MyFilter3------过滤器的init()方法,随着容器的启动进行了初始化");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// do something 处理request 或responseSystem.out.println("filter3");// 调用filter链中的下一个filterfilterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {System.out.println("MyFilter3-------过滤器的destroy()方法,随着容器的关闭而进行");}
}
public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {System.out.println("postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {System.out.println("afterCompletion");}
}
@Controller
public class MyController {@ResponseBody@RequestMapping("myTest")public String test(){System.out.println("controller.method invoked");return "ok";}
}

order定义filter的优先级,值越小优先级越高,因此运行顺序为

filter3—doFilter—>filter2—doFilter—>filter1—doFilter—>控制器方法—>返回到filter1—>返回到filter2—>返回到filter3

注意:@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响

可以看到只有执行的时候,才会按filter1,filter2,filter3的顺序执行。同时我们注意到:先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。也即是:postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的。如果实际开发中严格要求执行顺序,那就需要特别注意这一点。那为什么会这样呢? 得到答案就只能看源码了,我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()、preHandle()方法便是在其中调用的。

  • doDispatch方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {try {...........try {// 获取可以执行当前Handler的适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (logger.isDebugEnabled()) {logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}// 注意: 执行Interceptor中PreHandle()方法if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);// 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】mappedHandler.applyPostHandle(processedRequest, response, mv);}}...........}

看看两个方法applyPreHandle()、applyPostHandle()具体是如何被调用的,就明白为什么postHandle()、preHandle() 执行顺序是相反的了。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = this.getInterceptors();if(!ObjectUtils.isEmpty(interceptors)) {for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {HandlerInterceptor interceptor = interceptors[i];if(!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}}return true;}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {HandlerInterceptor[] interceptors = this.getInterceptors();if(!ObjectUtils.isEmpty(interceptors)) {for(int i = interceptors.length - 1; i >= 0; --i) {HandlerInterceptor interceptor = interceptors[i];interceptor.postHandle(request, response, this.handler, mv);}}}

发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致postHandle()、preHandle() 方法执行的顺序相反。

五、监听器

Java Web开发中的监听器(listener)就是由application、session、request三个对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件,如下所示:

  • ServletContextListener:对Servlet上下文的创建和销毁进行监听。
  • ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和修改。
  • HttpSessionListener:对Session的创建和销毁进行监听。
  • HttpSessionAttributeListener:对Session对象中属性的添加、删除和修改进行监听。
  • HttpSessionBindingListener:监听Http会话中对象的绑定信息。
  • HttpSessionActivationListener:监听器监听Http会话的情况。
  • ServletRequestListener:对请求对象的初始化和销毁进行监听。
  • ServletRequestAttributeListener:对请求对象属性的添加、删除和修改进行监听。

  1. 统计网站最多在线人数监听器的例子
/*** 上下文监听器,在服务器启动时初始化onLineCount和maxOnLineCount两个变量,* 并将其置于服务器上下文(ServletContext)中,其初始值都是0。*/
@WebListener
public class InitListener implements ServletContextListener {public void contextDestroyed(ServletContextEvent evt) {}public void contextInitialized(ServletContextEvent evt) {evt.getServletContext().setAttribute("onLineCount", 0);evt.getServletContext().setAttribute("maxOnLineCount", 0);}
}
/*** 会话监听器,在用户会话创建和销毁的时候根据情况修改onLineCount和maxOnLineCount的值。*/
@WebListener
public class MaxCountListener implements HttpSessionListener {public void sessionCreated(HttpSessionEvent event) {ServletContext ctx = event.getSession().getServletContext();int count = Integer.parseInt(ctx.getAttribute("onLineCount").toString());count++;ctx.setAttribute("onLineCount", count);int maxOnLineCount = Integer.parseInt(ctx.getAttribute("maxOnLineCount").toString());if (count > maxOnLineCount) {ctx.setAttribute("maxOnLineCount", count);DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");ctx.setAttribute("date", df.format(new Date()));}}public void sessionDestroyed(HttpSessionEvent event) {ServletContext app = event.getSession().getServletContext();int count = Integer.parseInt(app.getAttribute("onLineCount").toString());count--;app.setAttribute("onLineCount", count);}
}
  • 新建一个servlet处理
@WebServlet(name = "SessionServlet",value = "/sessionCount")
public class SessionServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html");//获取上下文对象ServletContext servletContext = this.getServletContext();Integer onLineCount = (Integer) servletContext.getAttribute("onLineCount");System.out.println("invoke doGet");PrintWriter out = resp.getWriter();out.println("<html><body>");out.println("<h1>" + onLineCount + "</h1>");out.println("</body></html>");}
}
  1. springboot监听器的使用(以实现异步Event监听为例子)
  • 场景

很多时候当我们完成某些业务后需要给用户推送相关消息提醒。对于这种非核心业务功能我们可以拿出来,创建一个事件去异步执行,从而实现核心业务和子业务的解耦。

  • 实现

定义事件类 Event

创建一个类,继承ApplicationEvent,并重写构造函数。ApplicationEvent是Spring提供的所有应用程序事件扩展类。

public class Event extends ApplicationEvent {private static final long serialVersionUID = 1L;private String msg ;private static final Logger logger=LoggerFactory.getLogger(Event.class);public Event(String msg) {super(msg);this.msg = msg;logger.info("add event success! message: {}", msg);}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}
}

创建一个用于监听指定事件的类,需要实现ApplicationListener接口,说明它是一个应用程序事件的监听类。注意这里需要加上@Component注解,将其注入Spring容器中。

@Component
public class MyListener implements ApplicationListener<Event>{private static final Logger logger= LoggerFactory.getLogger(MyListener.class);@Overridepublic void onApplicationEvent(Event event) {logger.info("listener get event,sleep 2 second...");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}logger.info("event msg is:{}",event.getMsg());}
}
  • 事件发布
    事件发布很简单,只需要使用Spring 提供的ApplicationEventPublisher来发布自定义事件
 @RequestMapping("/notice/{msg}")@ResponseBodypublic void notice(@PathVariable String msg){logger.info("begin>>>>>");applicationEventPublisher.publishEvent(new Event(msg));logger.info("end<<<<<<<");}
}
  • 测试
  • 异步执行
    默认是没有开启异步的,我们需要手动配置开启异步功能,很简单,只需要在配置类上加上@EnableAsync注解就行了,该注解用于声明启用Spring的异步方法执行功能,需要和@Configuration注解一起使用,我们可以直接加在启动类上。然后在监听方法上加上@Async注解,说明当前方法使用异步去执行。
@Component
public class MyListener implements ApplicationListener<Event>{private static final Logger logger= LoggerFactory.getLogger(MyListener.class);@Override@Asyncpublic void onApplicationEvent(Event event) {logger.info("listener get event,sleep 2 second...");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}logger.info("event msg is:{}",event.getMsg());}
}


可以发现已经实现了异步功能,主线程为nio-8080-exec-1,监听线程为 task-1。从浏览器反应可以看出,接口直接返回了,并没有等监听线程执行完后才返回。

  • 自定义异步线程池

用默认的Spring线程池会有啥问题呢?SimpleAsyncTaskExecutor不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。很有可能导致OOM。创建配置类(加上@Configuration),实现AsyncConfigurer接口,重写Executor方法。这里我们可以将原先配置在启动类上的@EnableAsync注解放到这个类上而不再直接加在主启动类上。

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {private static final Logger logger = LoggerFactory.getLogger(AsyncConfig.class);/*** 自定义异步线程池*/@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();//核心线程数taskExecutor.setCorePoolSize(2);//最大线程数taskExecutor.setMaxPoolSize(10);//队列大小taskExecutor.setQueueCapacity(15);//线程名的前缀taskExecutor.setThreadNamePrefix("async-thread-");taskExecutor.initialize();return taskExecutor;}/*** 捕捉IllegalArgumentException异常* @return*/public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new MyAsyncExceptionHandler();}static class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler{@Overridepublic void handleUncaughtException(Throwable throwable, Method method, Object... objects) {logger.info("TASK Exception message - " + throwable.getMessage());logger.info("Method name - " + method.getName());for (Object param : objects) {logger.info("Parameter value - " + param);}}}
}

六、 过滤器、拦截器、监听器对比

参考文章
参考文章
参考文章
参考文章
参考文章

过滤器、拦截器、监听器的区别与使用相关推荐

  1. Spring Boot 系列:过滤器+拦截器+监听器

    原 Swagger 文章合并到 Spring Boot 系列:配置 Swagger2 一.过滤器 - Filter 过滤器是处于客户端和服务器资源文件之间的一道过滤网,帮助我们过滤掉一些不符合要求的请 ...

  2. java web 过滤器 拦截器 监听器_Java中的拦截器和过滤器,可不是同一个东西

    过滤器(Filter) 过滤器就如上面的水质过滤器一样,把管道中的水进行一遍过滤再使用.过滤器基于filter接口中的doFilter回调函数,主要的用途是设置字符集.控制权限.控制转向.做一些业务逻 ...

  3. Filter(过滤器) 和 interceptor(拦截器)的区别

    Filter(过滤器) 和 interceptor(拦截器)的区别 1.拦截器是基于java反射机制的,而过滤器是基于函数回调的. 2.过滤器依赖于Servlet容器,而拦截器不依赖于Servlet容 ...

  4. 过滤器(Filter)和拦截器(Interceptor)的区别

    来自:http://www.cnblogs.com/luoyun/archive/2013/01/04/2844274.html 过滤器(Filter)和拦截器(Interceptor)的区别 Fil ...

  5. SpringBoot的过滤器拦截器AOP和异常处理器

    目录 前言 过滤器 创建过滤器 拦截器 创建拦截器 配置拦截器 AOP 创建AOP 异常处理器 创建异常处理器 测试请求的执行过程 创建接口 发送请求 前言 过滤器.拦截器.AOP.异常处理器是搭建系 ...

  6. 一口气说出 过滤器 和 拦截器 6个区别,别再傻傻分不清了

    周末有个小伙伴加我微信,向我请教了一个问题:老哥,「过滤器 (Filter) 和 拦截器 (Interceptor) 有啥区别啊?」 听到题目我的第一感觉就是:「简单」! 毕竟这两种工具开发中用到的频 ...

  7. 过滤器 拦截器 区别

    转 http://www.cnblogs.com/wangyuyu/archive/2013/07/02/3167354.html 1.拦截器是基于java的反射机制的,而过滤器是基于函数回调 2.过 ...

  8. tomcat 拦截指定url_一口气说出 过滤器 和 拦截器 6个区别,别再傻傻分不清了

    点击" 程序员内点事 "关注,选择" 设置星标 " 坚持学习,好文每日送达! 周末有个小伙伴加我微信,向我请教了一个问题:老哥,「过滤器 (Filter) 和 ...

  9. filter过滤器_web容器的过滤器Filter和拦截器Inteceptor的区别

    1 过滤器介绍 过滤器是JavaEE标准,采用函数回调的方式进行.是在请求进入容器之后,还未进入Servlet之前进行预处理,并且在请求结束返回给前端这之间进行后期处理. chain.doFilter ...

  10. 过滤器 和 拦截器 6个区别,别再傻傻分不清了

    周末有个小伙伴加我微信,向我请教了一个问题:老哥,过滤器 (Filter) 和 拦截器 (Interceptor) 有啥区别啊? 听到题目我的第一感觉就是:简单! 毕竟这两种工具开发中用到的频率都相当 ...

最新文章

  1. c语言基础习题下载,C语言基础题目
  2. JavaEE基本了解
  3. android修改适配器颜色,android viewpager更改适配器
  4. native react 图片多选_react-native多图选择、图片裁剪(支持ad/ios图片个数控制)
  5. python软件菜单如何设计_佩服!我用Python设计了一个签名软件
  6. .net mysql和php mysql数据库连接_浅谈PHP连接MySQL数据库的三种方式
  7. android通过WebView的evaluateJavascript()调用JS
  8. C语言的32个关键字怎么背,谁知道c语言的32个关键字怎么读,还有语法。
  9. nodejs下载文件到本地并命名 和 删除文件
  10. 车载主机企业对Android平台趋之若骛
  11. scratch小游戏2048
  12. 【数据结构】树与二叉树的基本概念及性质
  13. javase扎金花程序
  14. 使用libjpeg处理图像(libjpeg的使用压缩与解压缩jpg格式)
  15. IE浏览器故障及其解决方法
  16. 瑞康医药与亚马逊云科技达成战略合作,全国上百家子公司业务上云
  17. python全栈开发什么意思_Python是什么?老男孩python全栈开发
  18. 常用的算法最好的讲解地址
  19. W5500常见问题集锦
  20. excel表格打不开是什么原因_为什么你做的Excel表格,总是这么丑?

热门文章

  1. zigbee之SampleApp_ProcessEvent()
  2. Python学习-基础篇4 模块与包与常用模块
  3. 深度解析PHP数组函数array_chunk
  4. ADO.NET教程(一)
  5. 只运行一个实例的方法
  6. python 做词云 -jupyter跟随王树义教程学习
  7. Python学习笔记:Import详解2
  8. R:matlab交互,数据调用
  9. MPI 集合通信函数 MPI_Reduce(),MPI_Allreduce(),MPI_Bcast(),MPI_Scatter(),MPI_Gather(),MPI_Allgather(),MPI_S
  10. Zero-Copysendfile浅析