第一次课
在手机上记的,内容非常简略,不过好在第一节课本身也没提供什么有价值的新内容,大部分时间都是在复习小学期的内容。
异常
-
检查异常
-
运行异常
编译器不做任何检查,可由码力完全避免
抽象与灵活性的正相关性
第二次课
- Client -> Server (Request)
- Server -> Client (Servlet -> Response)
protocol: Host Addr / File Addr
protocol Host Addr File Addr
e.g.: https:// rama.works /prototype
使用Servlet Response,配合PrintWriter来输出数据
服务器Push技术,区别与以前的基于请求的连接
HTTP, Content-Type: MIME TYPE (考试不考)
text/html
text/plain
text/xml
text/json
application/pdf
application/octet-stream -> download
必考
servlet概念,方法(destroy(), new, init(), service()),生命周期
getParameter(String name)
init(ServletConfig config)
public class Temp implements Servlet {
private ServletConfig config;
public void init(ServletConfig config) {
this.config = config;
}
public void destroy() {
this.config.destroy();
}
public void service(ServletRequest res, ServletResponse, response)
throws ServletException, IOException {
// emitted
}
}
Query String -> ServletRequest Parameter -> getParameter()
getParameter
送分题
getOutputStream
获得输出
请求状态
404 Not Found
403 Forbidden
200 OK
第三次课
正确的代码顺序(见注释)
// first declare package
package io.github.medioqrity;
// second import native packages
import java.util.*;
import java.io.*;
// third import Java EE packages
import javax.servlet.*;
import javax.servlet.http.*;
// 3rd party packages
import org.apache.struts.action.*;
// your packages
import io.github.medioqrity.somepackage;
// your classes
public class HelloServlet extends HttpServlet {
public void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// ...
}
}
其中HttpServlet
是适配器类,这样就不用像这样把所有接口方法都实现了:
public class SampleServlet implements Servlet {
private ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
// ...
}
@Override
public void service(...) {
// ...
}
}
要记住init()
方法的参数,考试要考。
当然,在HttpServlet中,从private ServletConfig ...
到public void init()
都已经被写好了。
初始化数据库连接的话,在public void init()
中进行初始化,不要覆盖public void init(ServletConfig config)
。否则不要忘记super.init()
使用public void service()
分发各种请求:
public class SampleServlet extends HttpServlet {
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest hreq = (HttpServletRequest) req;
HttpServletResponse hres = (HttpServletResponse) res;
method = res.getMethod();
if ("GET".equals(method)) {
doGet(hreq, hres);
} else if ("POST".equals(method)) {
doPost(hreq, hres);
}
}
}
还是不太清楚public void service()
与default void service()
的区别
关于为什么不要使用out.close();
注意PrintWriter out = response.getWriter()
,显然这个PrintWriter
并不是你创建的:它只是一个引用。
所以自然应该是HttpServletResponse类会负责将它关闭。
Tomcat应用程序发布
- tomcat manager
- 待补充
websocket & sse
- websocket -> 双工
- sse -> 服务器单向往客户端
websocket + sse = 服务器端到客户端
使用Annotation
自学
- HTML语法
- Javascript
- Annotation
javax.servlet.http.HttpSession
javax.servlet.annotation.*
第四次课
HttpSession
会话进程
id -> getId()
怎么确定客户已经走了 -> timeout,可以通过在web.xml
中加入如下代码更改:
<session-config>
<session-timeout> 15 </session-timeout> <!-- in minutes -->
</session-config>
最有用的就是在session中存储数据,于是:
getAttribute(String name)
setAttribute(String name, Object value)
removeAttribute(String name)
在Servlet中使用HttpSession
public class SessionExampleServlet extends HttpServlet {
public void service(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException {
HttpSession session = req.getSession(); // obviously
// if this is the first time you visit
Cart cart = new Cart(); // create a new cart for you
session.setAttribute("cart", cart);
// or you already have the cart
Cart cart = (Cart) session.getAttribute("cart");
cart.add("aaa", 123);
}
}
值得注意的是,session
是从HttpServletRequest
中取得的。
在getSession()
方法中,如果session
对象还没有被创建的话就创建一个:
Returns the current session associated with this request, or if the request does not have a session, creates one
Session技术的实现
Cookie
~~今天也是好天气☆~
主流,存放在HTML Header中。
如果浏览器把Cookie关闭的话,那么req.getSession()
每次都会创建全新的Session
。
读取
从HttpServletRequest
中获取Cookies。注意可能会有多块小饼干。
Cookie[] getCookies()
Returns an array containing all of the Cookie objects the client sent with this request.
写入
向HttpServletResponse
中写入Cookies。
void addCookie(Cookie cookie)
Adds the specified cookie to the response.
之前提到的读取和写入都是必考。虽然这样标注看起来非常功利,我想其实真心想学和应试准备并不冲突。
在使用Cookie
之后就不用再使用getSession()
了,因为Cookie
本身就要从Session
中获取。
URL Rewriting
略
隐藏域
略
Request Dispatcher
分发请求。
(以下例子待验证是否正确)
public class TestServlet extends HttpServlet {
public void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
RequestDispatcher rd = req.getRequestDispatcher("/servlet/b");
rd.forward();
rd.include(req, res);
res.getWriter().println("你好,你吃了吗?");
}
}
回去自己补课
data share
Filter
Filter Config
第一次上机作业
总的来说就是实现一个搜索引擎,名为"软院找人"。当然因为数据量相当小,所以其实效率方面不用特别担心。
首先是定义学生的信息,包括姓名,学号,电话,QQ,以及邮箱,一共5个字段。
学号是主码。不过话说回来,既然是用txt或者xlsx存储,那应该也用不着数据库吧…
不限制内存的使用,但是一定要确保性能。
必须支持模糊搜索,但不必支持到subsequence的程度(substring即可)
存储结构的设计
为了确保查询效率,我思考了很多实现方案。但是既然要模糊搜索,那么肯定要涉及到字串匹配了。
如果不是模糊搜索的话,直接5个字典就完事了。
字串匹配,最经典的KMP,还有一些其他的启发式算法,再怎么样复杂度也都是 O ( n + m ) \mathcal{O}(n + m) O(n+m)的水准。为了能够把所有信息搜出来,显然每次查询都要把所有的学生数据都跑一遍。这样,每次查询的复杂度就是 O ( ∑ i s i + ∣ s ∣ × m ) \mathcal{O}(\sum_i s_i + |s| \times m) O(∑isi+∣s∣×m),其中sum是所有学生信息的长度总和,而 ∣ s ∣ |s| ∣s∣是学生信息的条数。
这等于每次查询都要把每个表都跑一遍才行,我反正是觉得这样不太好…
所以最后我觉得还是应该考虑使用Trie树。当然肯定就有人会感到好奇,Trie树怎么实现模糊查询呢?
以前在自学后缀数组的时候,学到了一个非常重要的概念:
所有的substring都是某个后缀的某个前缀
听起来有点拗口,总归来个简单例子帮助理解:
对于字符串abcabedcf
,abed
是后缀abedcf
的前缀,cab
是cabedcf
的一个前缀。
于是要利用只能查询前缀的Trie树来查询SubString,只需要把字符串的每个后缀都放进Trie树里就好了。
查询效率迷思
作为一个ACM半途而废的菜鸡,分析时间复杂度的功夫一直不到家。不过尝试总归还是要尝试的!
采用Trie树的话,插入一条长度为 ∣ s ∣ |s| ∣s∣的数据的时间复杂度是 O ( ∣ s ∣ 2 ) \mathcal{O}(|s|^2) O(∣s∣2)级别的。而查询一条长度为 ∣ q ∣ |q| ∣q∣的关键字,并且以关键字末端节点为根节点的子树里一共有 n n n个节点的话,则时间复杂度是 O ( ∣ q ∣ + n ) \mathcal{O}(|q| + n) O(∣q∣+n)的。
听起来很拗口,总归上图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bXMnwGQQ-1574695269976)(JavaEE笔记整理\Trie.png)]
这张图中黑色的结点表示根据关键字hhkb
,从根节点出发所走的路径。灰色三角形表示该节点的其他子树。红色节点表示关键字末端节点。红色三角形表示关键字末端节点的所有子树。
那么对于关键字hhkb
,
∣
q
∣
=
4
|q| = 4
∣q∣=4,
n
n
n等于红色三角形中,所有节点的个数。
我想,采用这种方法,跟暴力KMP匹配的效率相比,关键就是那些灰色三角形: 无脑KMP则会导致试图匹配灰色三角形中的字符串,而Trie树根本不会考虑去那些子树里查询答案。
也就是说,我相信采用Trie树来存储数据并用于处理查询的效率能够比KMP要高,就是因为Trie树直接抛弃了那些灰色三角形,自带剪枝效果。
但是实际效率到底是不是真的比无脑KMP要快呢?
一点代码
Trie.java:
package io.github.medioqrity;
import java.util.*;
import io.github.medioqrity.TrieNode;
public class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
/**
* insert a string into the trie tree
*/
public void insert(String name, int index, int start_index) {
TrieNode currentNode = root;
for (int i = start_index; i < name.length(); ++i) {
if (!currentNode.hasNext(name.charAt(i))) // if there is no such node
currentNode.setNext(name.charAt(i), new TrieNode());
currentNode = currentNode.getNext(name.charAt(i));
}
currentNode.setFlag(true);
currentNode.addIndex(index);
}
/**
* returns a list of integer that contains the index that satisfies
* the query
*/
public void query(String name, Set<Integer> result) {
TrieNode currentNode = root;
for (int i = 0; i < name.length(); ++i) {
if (!currentNode.hasNext(name.charAt(i)))
return; // nothing found
currentNode = currentNode.getNext(name.charAt(i));
}
dfs(currentNode, result);
}
/**
* recursively get all possible results
*/
private void dfs(TrieNode currentNode, Set<Integer> result) {
if (currentNode.getFlag()) {
for (int i : currentNode.getIndexes()) {
result.add(i);
}
}
for (TrieNode nextNode : currentNode.getMap().values()) {
dfs(nextNode, result);
}
}
private void print(Character c, int layer) {
for (int i = 0; i < layer; ++i) System.out.print(" ");
System.out.println(c);
}
private void debug(TrieNode currentNode, int layer) {
for (Character c : currentNode.getMap().keySet()) {
print(c, layer);
debug(currentNode.getNext(c), layer + 1);
}
}
public void debug() {
debug(root, 0);
}
public TrieNode getRoot() {
return root;
}
}
StudentQuery.java:
package io.github.medioqrity;
import java.util.*;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/query")
public class StudentQuery extends HttpServlet {
private static Trie nameTrie, phoneTrie, idTrie, qqTrie, mailTrie;
private static List<Student> students;
static {
nameTrie = new Trie();
phoneTrie = new Trie();
idTrie = new Trie();
qqTrie = new Trie();
mailTrie = new Trie();
students = new ArrayList<>();
loadData();
}
public StudentQuery() {
super();
}
private static void insert(Trie trie, String str, int index) {
for (int i = 0; i < str.length(); ++i) {
trie.insert(str, index, i);
}
}
private static Student parse(String line) {
String[] l = line.split("\\t");
if (l.length != 5) return null;
return new Student(l[0], l[1], l[2], l[3], l[4]);
}
private static void loadData() {
String fileName = "out.txt";
BufferedReader reader = null;
int index = 0;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName), "UTF-8"));
String temp = null;
while ((temp = reader.readLine()) != null) {
Student student = parse(temp);
if (student != null) {
students.add(student);
insert(nameTrie, student.getName(), index);
insert(phoneTrie, student.getPhone(), index);
insert(idTrie, student.getId(), index);
insert(qqTrie, student.getQq(), index);
insert(mailTrie, student.getMail(), index);
++index;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void query(Trie trie, String name, Set<Integer> result) {
trie.query(name, result);
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html; charset=UTF-8");
req.setCharacterEncoding("UTF-8");
res.setCharacterEncoding("UTF-8");
String keyword = req.getParameter("keyword");
Set<Integer> result = new HashSet<>();
PrintWriter out = res.getWriter();
out.println("<body>");
query(nameTrie, keyword, result);
query(phoneTrie, keyword, result);
query(idTrie, keyword, result);
query(qqTrie, keyword, result);
query(mailTrie, keyword, result);
for (int i : result) {
out.println(students.get(i) + "<br>");
}
out.println("</body>");
}
}
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
</head>
<body>
<form action = 'query' method = 'post'>
Enter name or something else: <input type = 'text' name = 'keyword'><br>
<input type = 'submit' value = 'Google 搜索'>
</form>
</body>
</html>
最后加载到服务器上就能获得结果。截图就不放了,存在泄漏真实信息的风险(毕竟是以真实数据作为基础生成的学生数据)
第五次课
偷懒没带电脑,只能回来重新敲到电脑上了。
req.getContextPath()
对于http://localhost:8080/j2ee1/servlet/a?abc=1
而言,req.getContextPath()
的返回值,可能是/j2ee
,也可能是空串,这取决于web应用被部署在服务器的哪个位置。
路径迷思
public class AServlet extends HttpServlet {
public void service(req, res) throws {
req.getRequestDispatcher("/servlet/b").forward(req, res);
}
}
在service()
中,转发的servlet的路径只要像上面那样写就可以了。
但是在index.html
中,应该采用document root:
<form action="/j2ee1/servlet/a" method="get></form>"
filter
一定会考doFilter(req, res, FilterChain chain)
,注意这里的FilterChain
,一个过滤器链,就是一串过滤器。
可是过滤器到底是什么呢
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MyFilter implements Filter {
public void destroy() {
}
public void init(FilterConfig config) throws ServletException {
this.config = config;
}
public void doFilter(req, res, chain) throws ServletException, IOException {
chain.doFilter(req, res);
}
}
说实话没太理解为什么一个过滤器里要放一个过滤器链。
Filter在请求到达后端之前被执行,因此又被称为interceptor。
暴力转码
整理笔记的时候才发现这老师讲课怎么这么跳
String wd = req.getParameter("wd");
wd = new String(wd.getBytes("ISO-8859-1"), "UTF-8");
另外一种就是req.setCharacterEncoding()
ServletConfig
<servlet>
<init-param>
<param-name>rate</param-name>
<param-value>7</param-value>
</init-param>
</servlet>
然后这些肯定是被封装在ServletConfig
中,因为init(ServletConfig config)
,所以显然config肯定有config.getInitParameter("rate")
不过servlet本身也实现了getInitParameter
,所以不用把config拉出来再获取初始化参数。
类似的,filter配置也是在web.xml中实现的:
FilterConfig
<filter-mapping>
<filter-name>f1</filter-name>
<servlet-name>s1</servlet-name> <!-- 指定过滤某个servlet -->
<url-pattern>/*.jpg</url-pattern>
<dipatcher>REQUEST</dispatcher> <!-- 只拦截浏览器的请求 -->
<dipatcher>FORWARD</dispatcher> <!-- 和上面的REQUEST一起,会过滤使用REQUEST和FORWARD过来的请求 -->
</filter-mapping>
注意doFilter()
里参数是ServletRequest, ServletResponse
,而不是派生出来的HttpServletRequest
之类的。所以读取之后要强制转换类型:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MyFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
HttpServletRequest hreq = (HttpServletRequest) req;
HttpServletResponse hres = (HttpServletResponse) res;
HttpSession ss = req.getSession();
String userName = (String) ss.getAttribute("username");
if (userName == null) {
// guest
req.getRequestDispatcher("login.html").forward(req, res);
return;
}
chain.doFilter(req, res);
}
}
第六次课
outline & assignment 1 review
outline
Listener
assignment 1 review
package io.github.medioqrity;
public class FindServlet {
// absolute path vs relative path
String wrongPath = "/WEB-INF/contact/a.txt"; // still absolute path
String rightPath = context.getRealPath("/WEB-INF/contact/a.txt"); // transform to the real absolute path using relative path
// file reading
FileInputStream fis = new FileInputStream(rightPath):
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
BufferedReader br = new BufferedReader(isr);
String temp = null;
while ((temp = br.readLine()) != null) {
}
}
缓存文件到内存中
publc static InputStream readFile(String file) {
File f = new File(fileName);
FileInputStream fis = new FileInputStream(f);
BufferedInputStream bis = BufferedInputStream(fis);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[8 * 1024];
int r = 0;
while ((r = fis.read(buffer)) != -1) {
baos.write(buffer, 0, r);
}
bis.close(); fis.close();
buffer = baos.toByteArray();
return new ByteArrayInputStream(buffer);
}
war文件
Web ARchive,网站归档文件。实际上就是一个压缩包,可以高效存储小文件,同时还可以检测是否有文件在传输过程中损坏。
使用getServletContext().getResourceAsStream("/WEB-INF/c.txt")
,可以方便地读取资源。
ClassLoader也是很不错的。
分页
因为分页这件事情要跨越多次访问过程,所以应该使用Session来存储。
HttpSession session = request.getSession();
session.setAttribute("result", result);
Listener
Event Listener
Button b = new Button();
ActionEven
ActionListener
产生了一个事件之后会广播给所有听众,因为并不清楚哪个听众会处理哪个事件。
把生死看破,(咦)所以对象的创建和销毁都是重大事件
@WebListener
public class ApplicationInitializer implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
}
public void contextDestroyed(ServletContextEvent sce) {
// something
}
}
部署
<listener>
<listener-class>ApplicationInitializer</listener-class>
</listener>
回去自己看其他listener。其实基本都不难。
JSP
java server page。用来解决类似于out.println("<td>" + data + "</td>")
这种垃圾代码。
可以直接用%
来分割java代码和html代码:
<h1><%= new java.util.Date() %></h1>
其实jsp等价于servlet。上述语句实际上被转换为:
out.write("<h1>");
out.println(new java.util.Date());
部署
<servlet>
<servlet-name>a<servlet-name>
<jsp-file>/WEB-INF/jsps/a.jsp</jsp-file>
</servlet>
jsp语法
<%@ page import="java.util.*", "java.io.*"
pageEncoding="utf-8"
isErrorPage="true|false"
errorPage="/error.jsp"
session="true"
%>
其中的isErrorPage
表示当前页面是否是出错页面。
errorPage
代表如果当前页面出错则应该访问的页面。
不必担心抛出异常。所有的语句都会被放在一个巨大的try catch块里。
剩下的自己学去。(jsp implicit object)