用人工智能挖掘與大數據,科學家們總結了抵達創作“爆發期”的重要規律。
模型中反映梵高(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小時內與您取得聯系。