实现一个简单版的tomacat

结构图:
在这里插入图片描述
实现的效果,请求资源,返回html页面:
在这里插入图片描述
在这里插入图片描述
以下是代码结构:
在这里插入图片描述
实现了对请求处理,读取输入流,并按照http格式进行解析,获取请求方法行、请求头、请求体的解析,解析完后进行对应方法分析,并按照请求资源返回。通过设置respose对象返回内容,包括相应状态码、响应头、响应体。在网络监听的时候利用线程池进行处理,提高服务的并行度。
具体代码实现如下:
1、监听网络请求

package com.webserver.core;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * WebServer主类
 * @author ta
 *
 */
public class WebServer {
	private ServerSocket server;
	
	private ExecutorService threadPool;
	
	public WebServer() {
		try {
			System.out.println("正在启动服务端...");
			//新建一个socket进行监听网络请求
			server = new ServerSocket(8089);
			//创建一个线程池提高并发度
			threadPool = Executors.newFixedThreadPool(8);
			System.out.println("服务端启动完毕!");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void start() {
		try {
			while(true) {
				System.out.println("等待客户端连接...");
				//socket监听请求
				Socket socket = server.accept();
				//接收到一个请求后,将socket传到ClientHandler中进行处理
				System.out.println("一个客户端连接了!");
				ClientHandler handler 
					= new ClientHandler(socket);
				//放到线程池中
				threadPool.execute(handler);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		WebServer server = new WebServer();
		server.start();
	}
}

2、对网络请求进行处理

package com.webserver.core;

import java.io.File;
import java.io.IOException;
import java.net.Socket;

import com.webserver.exception.EmptyRequestException;
import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;
import com.webserver.servlet.HttpServlet;
import com.webserver.servlet.RegServlet;

/**
 * 客户端处理器,负责处理与客户端的交互工作
 * @author ta
 *
 */
public class ClientHandler implements Runnable{
	private Socket socket;
	public ClientHandler(Socket socket) {
		this.socket = socket;
	}
	public void run() {
		try {
			//1解析请求
			HttpRequest request = new HttpRequest(socket);
			HttpResponse response = new HttpResponse(socket);
			
			//2处理请求
			//2.1:根据request获取资源的抽象路径
			String path = request.getRequestURI();
			//2.2:根据请求路径判断是否为请求一个业务操作
			HttpServlet servlet 
				= ServerContext.getServlet(path);
			if(servlet!=null) {
				
				servlet.service(request, response);
						
			}else {
				//2.3:根据该抽象路径从webapps下寻找该资源
				File file = new File("./webapps"+path);
				if(file.exists()) {
					System.out.println("该资源已找到!");
					//发送一个标准的HTTP响应给客户端回应该资源
					//将响应的资源设置到response中
					response.setEntity(file);
				}else {
					System.out.println("该资源不存在!");
					response.setStatusCode(404);
					response.setStatusReason("NOT FOUND");
					response.setEntity(
						new File("./webapps/root/404.html"));
				}
			}
			//3响应客户端
			response.flush();
			System.out.println("响应完毕!");
		} catch(EmptyRequestException e) {
			System.out.println("空请求...");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			/*
			 * 处理完请求并响应客户端后与其断开连接
			 */
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

3、对Servlet的实现支持,通过ServerContext将请求与对应的servlet进行映射。

package com.webserver.core;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.webserver.servlet.HttpServlet;

/**
 * 服务端相关配置信息
 * @author ta
 *
 */
public class ServerContext {
	/**
	 * 请求与对应Servlet的关系
	 * key:请求路径
	 * value:对应的Servlet实例
	 */
	private static Map<String,HttpServlet> servletMapping = new HashMap<>();
	static {
		initServletMapping();
	}
	
	private static void initServletMapping() {
//		servletMapping.put("/myweb/reg", new RegServlet());
//		servletMapping.put("/myweb/login", new LoginServlet());
//		servletMapping.put("/myweb/showAllUser", new ShowAllUserServlet());
		/*
		 * 解析conf/servlets.xml文件,将根标签
		 * 下所有名为<servlet>的子标签得到,然后
		 * 将每个servlet子标签中的属性path作为key
		 * 将属性className的值得到后利用反射加载
		 * 这个类的类对象并进行实例化,将实例化的
		 * 对象作为value保存到servletMapping这个
		 * Map中完成初始化。
		 */
		try {
		//读取xml配置文件
			SAXReader reader = new SAXReader();
			Document doc = reader.read(
					new File("./conf/servlets.xml"));
			Element root = doc.getRootElement();
			List<Element> servlets = root.elements();
			for(Element servletEle : servlets) {
				String key 
					= servletEle.attributeValue("path");	
					//通过反射获取对应的servlet实例对象
				String className 
					= servletEle.attributeValue("className");
				Class cls = Class.forName(className);
				HttpServlet servlet = (HttpServlet)cls.newInstance();
				//放进servletMapping中
				servletMapping.put(key, servlet);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	/**
	 * 根据请求获取对应的Servlet
	 * @param path
	 * @return
	 */
	public static HttpServlet getServlet(String path) {
		return servletMapping.get(path);
	}
}

4、对request的处理

package com.webserver.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

import com.webserver.exception.EmptyRequestException;

/**
 * 请求对象
 * 请求对象的每一个实例用于表示客户端(浏览器)发送过来
 * 的一个标准HTTP请求内容
 * 
 * 一个请求由三部分构成:请求行,消息头,消息正文
 * @author ta
 *
 */
public class HttpRequest {
	/*
	 * 请求行相关信息定义
	 */
	//请求方式
	private String method;
	//请求的抽象路径部分
	private String url;
	//请求使用的HTTP协议版本
	private String protocol;
	/*
	 * 由于请求行中抽象路径部分因客户端请求方式
	 * 不同会有不同内容:
	 * 1:不带参数的抽象路径,如:
	 *   /myweb/index.html
	 * 2:form表单GET形式提交,抽象路径就带参数,如:
	 *   /myweb/reg?username=xxx&password=xxx&...  
	 */
	//抽象路径中的请求部分。"?"左侧内容
	private String requestURI;
	//抽象路径中的参数部分。"?"右侧内容
	private String queryString;
	//保存具体的每一组参数
	private Map<String,String> parameters = new HashMap<>();
	
	
	/*
	 * 消息头相关信息定义
	 */
	//key:消息头的名字   value:消息头对应的值
	private Map<String,String> headers = new HashMap<>();
	
	
	
	/*
	 * 消息正文相关信息定义
	 */
	
	/*
	 * 与连接相关的属性
	 */
	private Socket socket;
	private InputStream in;
	/**
	 * 构造方法,用来初始化请求对象
	 * 初始化的过程就是解析请求的过程。
	 * @throws EmptyRequestException 
	 */
	public HttpRequest(Socket socket) throws EmptyRequestException {
		try {
			this.socket = socket;
			/*
			 * 通过socket获取输入流,用于读取客户端
			 * 发送过来的请求内容
			 */			
			this.in = socket.getInputStream();
			/*
			 * 解析请求分为三步
			 * 1:解析请求行内容
			 * 2:解析消息头内容
			 * 3:解析消息正文内容
			 */
			parseRequestLine();
			parseHeaders();
			parseContent();
		}catch(EmptyRequestException e) {
			throw e;
		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}
	/**
	 * 解析请求行
	 * @throws EmptyRequestException 
	 */
	private void parseRequestLine() throws EmptyRequestException {
		System.out.println("开始解析请求行...");
		try {
			String line = readLine();
			//是否为空请求
			if("".equals(line)) {
				//抛出空请求异常
				throw new EmptyRequestException();
			}
			
			System.out.println("请求行:"+line);
			/*
			 * 将请求行的内容按照空格拆分为三部分,
			 * 并分别设置到属性:method,url,protocol上
			 */
			String[] data = line.split("\\s");
			method = data[0];
			url = data[1];
			protocol = data[2];
			//进一步解析抽象路径部分
			parseURL();
			
			System.out.println("method:"+method);
			System.out.println("url:"+url);
			System.out.println("protocol:"+protocol);
			
		} catch(EmptyRequestException e) {
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("解析请求行完毕");
	}
	/**
	 * 进一步解析请求行中的抽象路径部分
	 */
	private void parseURL() {
		System.out.println("进一步解析抽象路径部分...");
		/*
		 *  在解析url时,首先判断该抽象路径是否
		 *  含有参数,标志就是有没有"?"。
		 *  如果没有则说明没有参数,那么直接将当前
		 *  抽象路径url的值赋值给requestURI即可
		 *  
		 *  如果有则说明有参数,那么首先按照"?"
		 *  将抽象路径拆分为两部分,第一部分就是
		 *  请求部分,赋值给requestURI,第二部分
		 *  就是参数部分赋值给queryString.
		 *  然后在对queryString部分进行进一步拆分
		 *  按照"&"先拆分出每一个参数,然后每个参数
		 *  再按照"="拆分为参数名与参数值,只有将
		 *  参数名作为key,参数值作为value保存到
		 *  属性parameters这个Map中完成解析工作
		 */
		if(url.indexOf("?")==-1) {
			//没有参数
			requestURI = url;
			
		}else {
			//有参数
			String[] data = url.split("\\?");
			requestURI = data[0];
			//判断"?"右侧确实包含参数部分
			if(data.length>1) {
				queryString = data[1];
				//对queryString解码
				try {
					/*
					 * String decode(String str,String enc)
					 * 将给定的字符串中含有"%XX"的内容按照指定
					 * 的字符集还原为对应字符串并替换这些"%XX",
					 * 然后将替换后的字符串返回。
					 */
					queryString = URLDecoder.decode(queryString, "UTF-8");
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				}
			
				//拆分每一个参数
				parseParameters(queryString);
				
			}
		}
		System.out.println("requestURI:"+requestURI);
		System.out.println("queryString:"+queryString);
		System.out.println("parameters:"+parameters);
		System.out.println("解析抽象路径部分完毕!");
	}
	/**
	 * 解释参数
	 * 参数的格式应当为:name=value&name=value&...
	 * @param line
	 */
	private void parseParameters(String line) {
		String[] data = line.split("&");
		for(String para : data) {
			//每个参数按照"="拆分
			String[] arr = para.split("=");
			if(arr.length>1) {
				parameters.put(arr[0], arr[1]);
			}else {
				parameters.put(arr[0], null);
			}
		}
	}
	
	/**
	 * 解析消息头
	 */
	private void parseHeaders(){
		System.out.println("开始解析消息头...");
		try {
			/*
			 * 消息头有若干行,因此我们应当循环调用
			 * readLine方法读取每一个消息头。但是
			 * 如果调用readLine方法返回值为""(空字
			 * 符串)时则说明单独读取到了CRLF,此时
			 * 表示消息头全部解析完毕,应当停止读取
			 * 工作。
			 * 每个消息头读取到以后,按照冒号空格(: )
			 * 进行拆分,并将拆分的第一项作为消息头的
			 * 名字,第二项作为消息头的值并以key,value
			 * 形式保存到属性headers这个Map中完成消息
			 * 头的解析工作
			 */
			String line = null;
			while(!"".equals(line = readLine())) {
				String[] data = line.split(": ");
				headers.put(data[0], data[1]);
			}	
			System.out.println("headers:" +headers);
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("解析消息头完毕");
	}
	/**
	 * 解析消息正文
	 */
	private void parseContent() {
		System.out.println("开始解析消息正文...");
		/*
		 * 一个请求是否包含消息正文可以根据这个请求
		 * 发送过来的消息头中是否包含:
		 * Content-Length来判定。如果包含这个头
		 * 就说明是含有正文的,并且指定了正文的长度
		 * 
		 * Content-Type头是客户端告知服务端消息
		 * 正文的内容是什么类型的数据。
		 * 
		 * Content-Type的值若为:
		 * application/x-www-form-urlencoded
		 * 则表明该正文是一个字符串,内容是页面表单
		 * 提交上来的用户输入的内容,格式与GET请求
		 * 中在url的"?"右侧内容一致。如:
		 * username=zhangsan&password=123456
		 * 
		 * 
		 * 1:首先在解析消息正文时先判断本次请求的
		 *   消息头中是否含有Content-Length
		 *   如果含有则说明包含消息正文,没有则忽略
		 *   解析消息正文的工作
		 * 2:获取消息头Content-Length的值,这是一个
		 *   数字,用来得知消息正文的长度(字节量)
		 * 3:通过输入流读取Content-Length指定的字节
		 *   量将消息正文数据读取出来。
		 * 4:在获取Content-Type头的值,根据这个值来
		 *   判定消息正文的数据类型,这里只判断一种:
		 *   application/x-www-form-urlencoded
		 *   若是上述的值,则说明这个正文内容是页面表单
		 *   提交上来的用户数据,将其转换为字符串,字符
		 *   集使用ISO8859-1。
		 * 5:转换后的字符串就可以进行拆分参数了,将
		 *   拆分后的参数再次存入parameters这个Map中        
		 * 
		 */
		try {
			if(headers.containsKey("Content-Length")) {
				int length = Integer.parseInt(
						headers.get("Content-Length"));
				byte[] data = new byte[length];
				//读取所有正文内容的字节
				in.read(data);
				//获取正文类型
				String type = headers.get("Content-Type");
				
				//判断是否为form表单数据
				if("application/x-www-form-urlencoded".equals(type)) {
					String line = new String(data,"ISO8859-1");
					//转码
					line = URLDecoder.decode(line,"UTF-8");
					//拆分出每一个参数
					parseParameters(line);
				}
				
				
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
		System.out.println("解析消息正文完毕");
	}
	
	/**
	 * 通过输入流读取客户端发送过来的一行字符串
	 * 一行结束的标志为CR,LF
	 * @return
	 * @throws IOException 
	 */
	private String readLine() throws IOException {
		StringBuilder builder = new StringBuilder();
		//c1表示上次读取到的字符,c2表示本次读取到的字符
		int c1=-1,c2=-1;
		while((c2 = in.read())!=-1) {				
			//是否连读读取到了CR,LF
			if(c1==13&&c2==10) {
				break;
			}
			builder.append((char)c2);
			c1 = c2;
		}
		return builder.toString().trim();
	}
	
	
	
	public String getMethod() {
		return method;
	}
	public String getUrl() {
		return url;
	}
	public String getProtocol() {
		return protocol;
	}
	/**
	 * 根据消息头的名字获取消息头对应的值
	 * @param name
	 * @return
	 */
	public String getHeaders(String name) {
		/*
		 * 这里的设计没有直接将headers这个Map对外
		 * 范围,避免外界拿到这个Map后可以任意操作,
		 * 这样破坏了当前属性的封装性。
		 * 实际开发中,很多这种集合,Map的属性都不
		 * 对外直接返回。
		 */
		return headers.get(name);
	}
	public String getRequestURI() {
		return requestURI;
	}
	public String getQueryString() {
		return queryString;
	}
	/**
	 * 获取指定参数对应的值
	 * @param name
	 * @return
	 */
	public String getParameter(String name) {
		return parameters.get(name);
	}
	
}


5、对文件的mime类型的处理

package com.webserver.http;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * HTTP协议相关定义
 * @author ta
 *
 */
public class HttpContext {
	/**
	 * Content-Type的值与资源后缀名的对应关系
	 * key:资源后缀名
	 * value:对应的Content-Type值
	 */
	private static Map<String,String> mime_mapping = new HashMap<>();
	
	static {
		initMimeMapping();
	}
	/**
	 * 初始化资源类型
	 * 读取conf/web.xml中资源类型列表,添加到mime_mapping中
	 * 可以解决大部分市面上常见文件类型与资源类型的映射关系
	 * 
	 * 异常处理原则:能处理尽量处理,处理不了抛出去
	 */
	private static void initMimeMapping() {
		try {
			SAXReader reader=new SAXReader();
			Document doc = reader.read(
				new File("conf/web.xml"));
			Element root=doc.getRootElement();
			List<Element> list=
					root.elements("mime-mapping");
			for (Element e : list) {
				String ext=e.elementTextTrim("extension");
				String type=e.elementTextTrim("mime-type");
				mime_mapping.put(ext, type);
			}
			//System.out.println(mime_mapping); 
		}catch(Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 根据资源后缀名获取对应的Content-Type的值
	 * @param ext
	 * @return
	 */
	public static String getMimeType(String ext) {
		return mime_mapping.get(ext);
	}
	
	
	public static void main(String[] args) {
		String fileName = "header.css";
		String ext = fileName.substring(
				fileName.lastIndexOf(".")+1);
		System.out.println(getMimeType(ext));
		
	}
}


6、对空请求的处理

package com.webserver.exception;
/**
 * 空请求异常
 * @author ta
 *
 */
public class EmptyRequestException extends Exception{
	private static final long serialVersionUID = 1L;

	public EmptyRequestException() {
		super();
		// TODO Auto-generated constructor stub
	}

	public EmptyRequestException(String message, Throwable cause, boolean enableSuppression,
			boolean writableStackTrace) {
		super(message, cause, enableSuppression, writableStackTrace);
		// TODO Auto-generated constructor stub
	}

	public EmptyRequestException(String message, Throwable cause) {
		super(message, cause);
		// TODO Auto-generated constructor stub
	}

	public EmptyRequestException(String message) {
		super(message);
		// TODO Auto-generated constructor stub
	}

	public EmptyRequestException(Throwable cause) {
		super(cause);
		// TODO Auto-generated constructor stub
	}
	
	
}

7、对servlet的实现

package com.webserver.servlet;

import java.io.File;

import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;

/**
 * 所有Servlet的超类
 * @author ta
 *
 */
public abstract class HttpServlet {
	/**
	 * 用于处理请求的方法。ClientHandler在调用
	 * 某请求对应的处理类(某Servlet)时,会调用
	 * 其service方法。
	 * @param request
	 * @param response
	 */
	public abstract void service(HttpRequest request,HttpResponse response);

	/**
	 * 跳转到指定页面
	 * @param path 从webapps之后开始指定路径,
	 *             如"/myweb/xxx.html"
	 * @param request
	 * @param response
	 */
	public void forward(String path,HttpRequest request,HttpResponse response) {
		File file = new File("./webapps"+path);
		response.setEntity(file);
	}
}


package com.webserver.servlet;

import java.io.File;
import java.io.RandomAccessFile;

import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;
/**
 * 处理登录业务
 * @author ta
 *
 */
public class LoginServlet extends HttpServlet{
	public void service(HttpRequest request,HttpResponse response) {
		System.out.println("LoginServlet:开始处理登录...");
		//1获取用户登录信息
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		System.out.println("username:"+username);
		System.out.println("password:"+password);
		
		//2验证登录
		try (
			RandomAccessFile raf
				= new RandomAccessFile("user.dat","r");
		){
			for(int i=0;i<raf.length()/100;i++) {
				raf.seek(i*100);
				//读取用户名
				byte[] data = new byte[32];
				raf.read(data);
				String name = new String(data,"UTF-8").trim();
				if(name.equals(username)) {
					raf.read(data);
					String pwd = new String(data,"UTF-8").trim();
					if(pwd.equals(password)) {
						//登录成功
						forward("/myweb/login_success.html",request,response);
						return;
					}
					/*
					 * 只要用户名对了,无论密码是否匹配,都
					 * 应当停止后续读取工作。因为user.dat
					 * 文件中不存在重复的用户名,减少没有必要
					 * 的读取操作,提高性能。
					 */
					break;
				}
			}//for循环结束
			
			//登录失败
			forward("/myweb/login_fail.html", request, response);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
		
		System.out.println("LoginServlet:处理登录完毕!");
	}
}
package com.webserver.servlet;

import java.io.File;
import java.io.RandomAccessFile;
import java.util.Arrays;

import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;

/**
 * 处理用户注册业务   
 * @author ta
 *
 */
public class RegServlet extends HttpServlet {
	
	public void service(HttpRequest request,HttpResponse response) {
		System.out.println("RegServlet:开始处理注册...");
		//1通过request获取用户的注册信息
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		String nickname = request.getParameter("nickname");
		int age = Integer.parseInt(request.getParameter("age"));
		System.out.println("username:"+username);
		System.out.println("password:"+password);
		System.out.println("nickname:"+nickname);
		System.out.println("age:"+age);
		//2将注册信息写入文件user.dat		
		try (
			RandomAccessFile raf
				= new RandomAccessFile("user.dat","rw");	
		){
			/*
			 * 首先判断user.dat文件中是否已经存在该
			 * 用户,若存在则响应用户注册提示页面,
			 * 提示该用户已存在,否则才将该用户信息
			 * 写入user.dat文件
			 * 
			 * 注册提示页面:reg_have_user.html
			 * 提示也中显示一行字,内容为:该用户已存在,请重写注册!
			 */
			for(int i=0;i<raf.length()/100;i++) {
				raf.seek(i*100);
				byte[] data = new byte[32];
				raf.read(data);
				String name = new String(data,"UTF-8").trim();
				if(name.equals(username)) {
					//该用户已存在
					forward("/myweb/reg_have_user.html", request, response);
					return;
				}
			}
			
			
			
			
			
			//先将指针移动到文件末尾
			raf.seek(raf.length());

			//写用户名
			byte[] data = username.getBytes("UTF-8");
			//将字节数组扩容至32字节
			data = Arrays.copyOf(data, 32);
			//将32字节一次性写入文件
			raf.write(data);
			
			//写密码
			data = password.getBytes("UTF-8");
			data = Arrays.copyOf(data, 32);
			raf.write(data);
			
			
			//写昵称
			data = nickname.getBytes("UTF-8");
			data = Arrays.copyOf(data, 32);
			raf.write(data);

			
			//写年龄
			raf.writeInt(age);
		
			//注册完毕,响应注册成功页面给客户端
			forward("/myweb/reg_success.html", request, response);
			
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
		
		
		
		//3设置response响应客户端注册结果
		
		
		System.out.println("RegServlet:处理注册完毕!");
	}
}

package com.webserver.servlet;

import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.FileTemplateResolver;

import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;

/**
 * 展示用户列表
 * @author ta
 *
 */
public class ShowAllUserServlet extends HttpServlet{
	public void service(HttpRequest request, HttpResponse response) {
		/*
		 * 1:读取user.dat文件,将所有用户读取出来
		 *   其中每个用户用一个Map保存,key保存
		 *   属性名,value保存该属性的值。
		 *   再将这些Map实例存入一个List集合备用
		 *   
		 * 2:利用thymeleaf将List集合的数据绑定到
		 *   showAllUser.html页面上。
		 *   页面上要对应的添加thymeleaf需要的属性
		 *   否则thymeleaf不知道如何绑定。  
		 */
		try (
			RandomAccessFile raf
				= new RandomAccessFile("user.dat","r");
		){
			List<Map<String,String>> userList = new ArrayList<>();
			for(int i=0;i<raf.length()/100;i++) {
				byte[] data = new byte[32];
				raf.read(data);
				String username 
					= new String(data,"UTF-8").trim();
				
				raf.read(data);
				String password = new String(data,"UTF-8").trim();
				
				raf.read(data);
				String nickname = new String(data,"UTF-8").trim();
				
				int age = raf.readInt();
				//用一个Map保存一个用户信息
				Map<String,String> user = new HashMap<>();
				user.put("username", username);
				user.put("password", password);
				user.put("nickname", nickname);
				user.put("age", age+"");
				//将该用户信息存入list集合
				userList.add(user);
				
			}//for循环结束
			
			/*
			 * thtmeleaf模板引擎,用来将数据绑定到
			 * 静态页面的核心组件
			 */
			TemplateEngine eng = new TemplateEngine();
			
			FileTemplateResolver resolver 
				= new FileTemplateResolver();
			//设置字符集,告知引擎静态页面的字符集
			resolver.setCharacterEncoding("UTF-8");
			//将解析器设置到引擎上,使得引擎知道如何处理静态页面
			eng.setTemplateResolver(resolver);
			
			//Context用来存储所有要在页面上显示的数据
			Context context = new Context();
			//将保存所有用户的list集合存入context
			context.setVariable("list", userList);
			
			/*
			 * 调用引擎的process处理方法,该方法就是将指定
			 * 的数据与指定的页面进行绑定。
			 * 参数1:静态页面的路径
			 * 参数2:需要在静态页面上显示的动态数据
			 * 该方法返回值为一个字符串,内容就是绑定了动态
			 * 数据的静态页面所对应的HTML代码
			 */
			String html = eng.process("./webapps/myweb/showAllUser.html", context);
			
			
			//将生成的页面设置到response中响应给客户端
			response.setContentData(html.getBytes("UTF-8"));
			response.putHeader("Content-Type", "text/html");
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
		
	}

}

8、对文件的读写test

package com.webserver.test;

import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * 显示所有员工信息
 * @author ta
 *
 */
public class ShowAllUserDemo {
	public static void main(String[] args) throws IOException {
		/*
		 * 使用RAF读取user.dat文件
		 * 循环读取该文件,循环次数应当是文件长度/100
		 * 每条记录读取时:
		 * 首先读取32字节,这是用户名,将该字节按照
		 * UTF-8编码转换为字符串,注意,转换后要trim,
		 * 因为这个字符串含有空白字符。
		 * 依次类推读取密码,昵称。
		 * 之后再读取一个int值,这个是年龄。
		 * 
		 * 输出格式例如:
		 * 张三,123456,阿三,22
		 */
		
		RandomAccessFile raf
			= new RandomAccessFile("user.dat","r");
		
		for(int i=0;i<raf.length()/100;i++) {
			//读取用户名
			byte[] data = new byte[32];
			raf.read(data);
			String username = new String(data,"UTF-8").trim();
			
			raf.read(data);
			String password = new String(data,"UTF-8").trim();
			
			raf.read(data);
			String nickname = new String(data,"UTF-8").trim();
			
			int age = raf.readInt();
			System.out.println("pos:"+raf.getFilePointer());
			System.out.println(username+","+password+","+nickname+","+age);
		}
		
		raf.close();
	}
}

9、资源文件结构
在这里插入图片描述
代码链接

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

科学熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值