用人工智能挖掘与大数据,科学家们总结了抵达创作“爆发期”的重要规律。
模型中反映梵高(Van Gogh)后印象主义艺术风格特征的像素图
在开发著名的“滴画法”之前,抽象艺术家杰克逊·波洛克曾涉足素描、版画以及超现实主义绘画。美国西北大学凯洛格管理学院的一项新研究发现,“滴画法”的运用使波洛克进入了一段“爆发期”(即一系列具有高度影响力的作品连续紧密地产出),在此期间创作的作品令他至今仍家喻户晓。利用人工智能挖掘与大数据,研究人员发现,这种模式并非特例,而是一条普遍规律——爆发期直接来自多年的探索(对于不同风格或主题的研究),紧接着是多年的深耕(专注于某一狭窄领域进行钻研和积累)。这项研究于9月13日发表在《自然通讯》杂志上。
“单独的探索或深耕都与爆发期没有关联,这两者必须遵循一定的顺序。尽管探索具有一定风险,可能不会带来任何好处,却能增加偶然发现伟大想法的可能性;相比之下,深耕则是一种比较保守的策略。有趣的是,‘先探索后深耕’似乎和爆发期的出现具有高度相关性。”研究的领导者、西北大学工业工程与管理科学教授王大顺(音译)说。
2018年,王教授和同事在《自然》杂志上发表了一篇论文,描述了艺术、文化和科学职业生涯中的爆发期现象,当时,他很好奇到底是什么驱动了爆发期。在参观梵高博物馆时,王教授发现了线索。1888~1890年是梵高的艺术突破时期,期间他创作了《星空》、《向日葵》等最为著名的作品。在此之前,他的作品更为写实,还不属于印象派。“如果你看他在1888年之前的作品,你会发现这些作品与他的巅峰时期作品有很大不同。”王教授说。
在本次的研究中,王教授团队基于深度学习和网络科学开发了一种算法,然后将其用于大数据探索,追踪艺术家、电影导演和科学家的职业产出。对于艺术家,团队使用了图像识别算法,对80万幅视觉艺术图像进行数据挖掘。这些图像涵盖2128名艺术家,其中便包括波洛克和梵高。此外,研究人员收集的数据还覆盖4337位导演和20040位科学家。
根据拍卖价格、电影评级和学术论文的引用量,研究人员量化了每位创作者职业生涯中的巅峰作品。他们发现,当进行了一段时间探索而没有深耕时,出现爆发期的概率就会降低。同样,缺乏前期的探索也不能保证爆发期的出现。只有当探索后紧跟着深耕时,爆发期出现的概率才会持续而显著地增加。爆发期持续的平均时间大约是有五年,在此之后,个体将会恢复到“正常”状态,不再展现出特有规律。这一规律适用于不同的创作领域。
论文合著者吉莉安·乔恩说:“这些发现让我们认识到,要想获得足够的影响力,就必须参与不同类型的活动,比如探索新领域或利用现有的知识进行深耕研发,而且要有一定的先后顺序。”
编译:攀汗 审稿:西莫 责编:陈之涵
期刊来源:《自然通讯》
期刊编号:2041-1723
原文链接:https://phys.org/news/2021-09-secret-van-gogh-success.html
中文内容仅供参考,一切内容以英文原版为准。转载请注明来源。
求转发(forward):发送一次请求,将表单数据或封装到url中的数据一并转发到新页面。
方法:request.getRequestDispatcher(URL地址).forward(request, response)
重定向(redirect):发送两次请求,一次请求会收到302状态码,第二次请求收到新地址。
方法:response.sendRedirect(URL地址)
转发与重定向过程图
转发过程:客户浏览器发送http请求----》web服务器接收此请求,---》调用内部的一个方法在容器内部完成请求处理和转发动作----》将目的资源展示给客户。转发的路径依然在当前的web容器中,中间的数据传输靠 request共享。转发行为浏览器只做了一次访问请求。
重定向过程:客户浏览器发送http请求----》web服务器接收后发送302状态码响应及对应新的location给客户端浏览器----》客户端浏览器发现是302的状态码之后,自动再发送一个新的http请求,请求url是新的location地址----》服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意的URL,既然是浏览器重新发送了请求,则没有什么request传递的概念了。地址栏的地址是需要改变的,重定向行为浏览器至少做了两次访问请求。
两者的区别:
forward是服务器请求资源,服务器直接访问目标中的URI获取响应,将响应中的内容发送给浏览器,浏览器不知道内容来自于哪里,所以地址栏不变。
redirect服务器根据逻辑,发送一个状态码302,告诉浏览器去请求地址(url),url可以是其他应用。所以地址栏是要改变的。
forward转发页面和转发到的页面可以共享request中的内容。
redirect不能共享。
forward 用于登录注册页面
redirect 用于注销登录返回主页面或跳转其他网站,不再使用response输出数据,否则会异常。
forward 效率高
redirect 效率低
原理:转发是服务器行为,重定向是客户端行为。
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//response.setCharacterEncoding("utf-8");
response.setContentType("text/hrml;charset=utf-8");
String username= request.getParameter("username");
String passwd= request.getParameter("passwd");
if("admin".equals(username)&& "123".equals(passwd)) {
/*
* System.out.println("登陸成功:"); //response.getWriter().write("登录成功");
* response.setStatus(302); response.setHeader("Location","login_success.html");
*/
//重定向
//response.sendRedirect("login_success.html");
//请求转发
request.getRequestDispatcher("login_success.html").forward(request, response);
}else {
response.getWriter().write("登录失败");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
路径问题(转发和重定向的URLString前有加 / 为绝对路径 反之为相对路径)
1、重定向的 / 表示:http://服务器ip:端口/
//生成的地址:web服务器本身地址+参数生成完整的URL
//即:http://localhost:8080/Manager/index.html
response.sendRedirect("/Manager/index.html")
2、请求转发的 / 表示:http://服务器ip:端口/项目名
//生成的地址:http://localhost:8080/项目名/index.html
request.getRequestDispatcher("/index.html").forward(request, response);
假设通过表单请求指定的Url资源 action=“LoginServlet”
则表单生成的请求地址为:http://localhost:8080/项目名/LoginServlet
重定向:response.sendRedirect(“Manager/index.html”)
// 生成相对路径:http://localhost:8080/项目名/Manager/index.html
请求转发:相对路径情况下生成的完整URL与重定向方法相同。
【注】重定向其实 是两次request。第一次,客户端request A,服务器响应,并response回来,告诉浏览器,你应该去B,此时,浏览器再次发送request,请求 B的资源。重定向可以访问当前web之外的资源,在重定向的过程中,传输的信息会丢失。
系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多(Java)码农和想成为(Java)码农的人。
到现在为止,我们的租房网应用只是实现了简单的用户登录(实际上仅有前端页面,后台还没有实现登录验证的业务逻辑)、查看自己感兴趣的房源(也是仅有一个接口)、查看某个房源的详情、编辑某个房源的信息等功能。
本篇文章将为我们的租房网应用实现一个简单的用户注册功能。
前面的工作只是一直不断的在使用新技术改造我们的项目,从最开始的Servlet,经过使用JSP和JSTL、Spring MVC和Spring IoC、关系数据库H2Database和JDBC、Maven、Spring JDBC,到目前采用ORM框架MyBatis、连接池框架Druid等技术,我们的项目在开发效率、可维护性、性能等方面不敢说达到完美,但也算很不错了。
所以,既然我们的技术架构搭建的也还够用了,现在暂时让我们的重心转移到业务架构上来。当然,还有应用架构和数据架构。
应用架构我们就暂时采取独立的单块应用架构吧,就是说把所有业务功能都放在一个应用中。目前很流行微服务架构,但那适合于业务功能很复杂,需要拆分为成百上千个应用,数据库也动辄上百个库,上万张表,我们的租房网应用现在还早着呢,全部功能放在一起又快又简单。
数据架构上目前也仅仅使用了房源数据,而房源数据里面也只是模拟了若干个字符串类型的数据,并没有涉及到文档、图片、视频、音频等其他类型的数据,我们就先用关系数据库吧。
业务架构我们也不作深入分析和设计,我们就怎么简单怎么来吧,现在缺用户注册功能,那我们就实现一个用户注册功能,还是赶紧把租房网应用改造成至少像模像样要紧。
首先从租房网平台的最终用户的角度来看,我们应该有一个注册页面。
这个注册页面跟登录页面类似,不涉及任何其他业务的数据,因此可以采用静态页面的方式来实现。
有了这个注册页面,我们就可以直接打开这个页面进行注册。当然,也可以从其他页面比如登录页面链接到注册页面。
用户填写注册信息完毕之后,需要将它们提交到我们的租房网应用的后台。首先到达的是我们的控制器。因此,我们需要为控制器设计一个Handler,采用POST方法映射到 register.action 。
注册信息至少应该包含用户名和密码,当然实际应用中还应该包含更多用户信息,所以,我们应该设计一个用户实体类,类名就叫 User 吧。
根据分层的思维,控制器的Handler需要调用一个服务来实现用户注册的业务逻辑,我们就设计一个 UserService 吧,它专门用来处理用户相关的业务,比如注册、登录等等。
现在我们已经使用了MyBatis作为DAO层,所以还需要设计一个 UserMapper 。
最后,我们需要把用户数据保存在数据库中,所以要建立一个 user 表。
注册页面的内容很简单,主要是使用表单元素<form>(HTML的基础知识大家可以参考这篇文章):
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>租房网 - 注册</title> </head> <body> <form action="register.action" method="post"> <h2>用户注册</h2> <label for="user_name">请输入用户名</label><input type="text" id="user_name" name="userName" /> <label for="password">请输入密码</label><input type="password" id="password" name="password" /> <label for="password_confirmed">请再次输入密码</label><input type="password" id="password_confirmed" name="passwordConfirmed" /> <input type="submit" value="注册" /> </form> <p><a href="login.html">已经注册,直接登录!</a></p> </body> </html>
这里重点关注的就是:
它们的值都需要与后台代码一致。
为了方便用户使用注册功能,一般的Web应用都会在登录页面设计一个链接跳转到注册页面,于是我们的登录页面 login.html 变为:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>租房网 - 登录</title> </head> <body> <form action="login.action" method="post"> <label for="user_name">用户名</label><input type="text" id="user_name" name="userName" /> <label for="password">密码</label><input type="password" id="password" name="password" /> <input type="submit" value="登录" /> </form> <p><a href="register.html">还没有注册?</a></p> </body> </html>
主要是在表单元素<form>之后添加了一个<a>元素。
现在我们可以发布一下应用并启动Tomcat,验证一下我们的页面是否有错误,除了最后提交注册请求时会出现404的错误之外,显示上应该没什么问题,登录页面变成这样:
注册页面是这样的:
看到上面的用户注册页面,我们很容易想到这里还有一个要考虑的问题,就是用户一旦点击注册按钮提交了注册请求之后,如何知道自己是否注册成功呢?如果不成功,那是因为什么导致不成功呢?即我们的系统应该给用户返回何种响应呢?
我这里的设计是提供一个注册结果的响应页面,它显然是动态的,它要么提示注册成功,要么提示导致注册失败的原因。所以,我设计了两个页面,一个是静态页面,一个是JSP页面。
register-success.html:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>租房网 - 注册成功!</title> </head> <body> <h1>注册成功!请<a href="login.html">登录</a>!</h1> </body> </html>
register-failure.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>租房网 - 注册失败!</title> </head> <body> <h2>注册失败!请重新<a href="register.html">注册</a>!</h2> <h3>失败原因:${errorMessage}</h3> </body> </html>
注意,这里使用EL表达式来访问数据,所以转发给此页面的时候必须附加上数据 errorMessage !
虽然我们的思路中是按照前端页面、控制器层、服务层、数据访问层(DAO层)来分析的,但是由于我们的控制器层依赖于服务层,而服务层又依赖于数据访问层,所以开发的时候我们可以自下而上,这样的话代码层面不会出现错误提示。
我们先来考虑User实体类,前面已经提到过用户提交的注册信息至少包含用户名和密码,所以User实体类也必须有这两项。
难道这样就够了吗?我们再以用户的角度思考一下,假如用户有一天突然觉得自己的用户名不好,希望修改为另外一个用户名怎么办?是不是需要将关联到该用户名的其他记录都需要修改为新的用户名?这种方案实际上也可以,不过就会产生牵一发而动全身的不良效果。
所以,为了灵活考虑,我们应该为每个用户赋予一个全局唯一的且不可变的用户ID,然后跟该用户有关的记录都关联到这个用户ID。从这里我们可以看出,这个用户ID是由我们的系统自动生成的(那该如何生成呢?下面介绍),对用户是不可见的。
然后,需要考虑用户ID的数据类型(实际上它与如何生成也有一定关系),我们这里仍然选择字符串。
综合起来我们的User实体类有如下特点:
package houserenter.entity; public class User { private String id; private String name; private String password; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User [name=" + name + ", password=" + password + "]"; } }
实体类中的getter方法、setter方法、toString()方法可以使用IDE提供的功能快速添加。
我们很容易根据业务功能设计出 UserMapper 包含哪些接口。
因为用户注册功能相当于是新增一个用户,所以需要一个插入用户的接口。
又因为用户注册时需要判断用户名是否已经被注册,所以需要一个根据用户名查找用户的接口。
不过,由于我们采用的是H2Database的嵌入式模式,所以必须由应用自己来创建用户表(实际生产环境中一般是由DBA来建表),所以还需要一个创建用户表的接口。但是用户表不能每次都重复创建,所以使用了 if not exists 语法。
UserMapper.java:
package houserenter.mapper; import houserenter.entity.User; public interface UserMapper { int cteateTable(); int insert(User user); User selectByName(String name); }
UserMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="houserenter.mapper.UserMapper"> <update id="cteateTable"> create table if not exists user(id varchar(36) primary key, name varchar(32), password varchar(16)) </update> <insert id="insert" parameterType="houserenter.entity.User"> insert into user(id, name, password) values(#{id}, #{name}, #{password}) </insert> <select id="selectByName" parameterType="java.lang.String" resultType="houserenter.entity.User"> select id,name,password from user where name = #{name} </select> </mapper>
服务层实现业务逻辑,所以就目前来说 UserService 最主要的一个方法是处理用户的注册请求。
用户注册的业务逻辑我这里设计的比较简单,仅仅包括判断两次密码是否一致和用户名是否已经被注册,实际上还有用户名和密码的长度、字符要求等限制:
另外,由于我们采用的是H2Database的嵌入式模式,所以必须由应用自己来创建用户表(实际生产环境中一般是由DBA来建表),所以在UserService组件实例化之后首先需要判断用户表是否存在,如果不存在则创建。
package houserenter.service; import java.util.UUID; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import houserenter.entity.User; import houserenter.mapper.UserMapper; @Service public class UserService { @Autowired private UserMapper userMapper; @PostConstruct public void init() { userMapper.cteateTable(); } public User register(String userName, String password, String passwordConfirmed) throws Exception { if (!passwordConfirmed.equals(password)) { throw new Exception("两次输入的密码不一致,请重新输入!"); } User user = userMapper.selectByName(userName); if (user != null) { throw new Exception("用户名 " + userName + " 已经注册过,请选择其他用户名!"); } user = new User(); user.setId(UUID.randomUUID().toString()); user.setName(userName); user.setPassword(password); userMapper.insert(user); return user; } }
这个UserService其实也没有太多可说的,它依赖于 UserMapper,然后依照业务流程来编写代码即可。
需要重点关注的是,我在这里使用了Java异常(可以参考这篇文章),一旦两次密码不一致,或者用户名已经被注册过,就抛出异常。
还有一点是,我使用了 java.util.UUID 类来生成全局唯一的用户ID。
最后要提醒的是,register() 方法的返回值类型是 User 。这是因为在此业务逻辑中我们生成了用户ID,而此用户ID有可能被上层组件用到。一般情况下,方法一旦生成了新数据,则需要将该新数据返回给调用者。
还有一个比较容易忽略的问题是数据库操作的事务。因为我们的这段业务逻辑中访问数据库的地方有两处,一处是判断用户名是否已经被注册过,一处是插入新用户。如果有两个用户注册的请求到来,且它们要注册的用户名相同,那么很有可能一个请求刚刚判断完用户名不存在(尚未插入到数据库),另一个请求接着也判断该用户名是否已经被注册过(显然是没有),最后导致该用户名被注册两次。我们可以在数据库层面为user表的name列加上唯一性约束,也可以在应用层面将若干操作封装为事务。我们这里暂且忽略这个问题。
控制器类 HouseRenterController 首先要注入 UserService :
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>租房网 - 注册</title> </head> <body> <form action="register.action" method="post"> <h2>用户注册</h2> <label for="user_name">请输入用户名</label><input type="text" id="user_name" name="userName" /> <label for="password">请输入密码</label><input type="password" id="password" name="password" /> <label for="password_confirmed">请再次输入密码</label><input type="password" id="password_confirmed" name="passwordConfirmed" /> <input type="submit" value="注册" /> </form> <p><a href="login.html">已经注册,直接登录!</a></p> </body> </html>
添加处理注册请求的Handler也很简单:
@PostMapping("/register.action") public ModelAndView postRegister(String userName, String password, String passwordConfirmed) { System.out.println("userName: " + userName + ", password: " + password + ", passwordConfirmed: " + passwordConfirmed); ModelAndView mv = new ModelAndView(); try { userService.register(userName, password, passwordConfirmed); mv.setViewName("register-success.html"); } catch (Exception e) { mv.addObject("errorMessage", e.getMessage()); mv.setViewName("register-failure.jsp"); } return mv; }
唯一要关注的是,注册成功和注册失败分别转发到了不同的页面。
本篇文章简单实现了用户注册的功能,还有很多可以优化改进的地方:
不管怎样,我们还是实现了一个基本可用的用户注册功能,而且开发起来还是相当快、相当清晰的,因为我们之前已经搭建好了整个技术框架啊,正所谓磨刀不误砍柴工!
*请认真填写需求信息,我们会在24小时内与您取得联系。