JDBC基础JDBC是一个Java中用于连接数据库的API，通过JDBC可以方便的以任何形式访问关系型数据库中的数据。基本流程连接到MySQL将查询指令发送到数据库处理数据库送回的信息创建JDBC Application导包import java.sql.*;注册MySQL驱动Class.forName("com.mysql.cj.jdbc.Driver"); // mysql 5以上 Class.forName("com.mysql.jdbc.Driver"); // mysql 5及以下连接到数据库String DBname = "students"; // 数据库名 String user = "root"; // 用户名 String pw = "12345678"; // 用户密码 String uri = "jdbc:mysql://localhost:3306/" + DBName + "?useSSL=false&characterEncoding=utf-8"; Connection con = DriverManager.getConnection(uri, user, pw);使用已经创建好的连接创建一个Statement用于处理SQL语句Statement stmt = con.createStatement();处理SQL语句String cmd = "SELECT * FROM class"; ResultSet rs = stmt.executeQuery(cmd);从结果集中提取数据while (rs.next()) { int id = rs.getInt("student_id"); String name = rs.getString("stu_name"); Date date = rs.getDate("date"); System.out.printf("ID: %d, Name: %s, Date: %s\n", id, name, date); }执行结果：ID: 1, Name: YorkWu, Date: 2022-06-19 ID: 2, Name: a, Date: 2022-06-19 ID: 3, Name: b, Date: 2022-06-19 ID: 4, Name: c, Date: 2022-06-19 ID: 5, Name: d, Date: 2022-06-19 ID: 6, Name: e, Date: 2022-06-19 ID: 7, Name: f, Date: 2022-06-19关闭连接rs.close(); stmt.close(); con.close();注意注册数据库驱动程序，一般使用Class.forName()即可。数据库url格式mysql:jdbc:mysql://hostname/databaseName[?useSSL=false&characterEncoding=utf-8]记得关闭连接JDBC StatementsStatement用于对数据库进行通用访问，可以处理静态SQL语句（一般的SQL语句），实时编译SQL语句执行，由于需要拼接SQL语句，容易造成SQL注入。PreparedStatement，预编译SQL语句，在需要多次使用的语句中使用。创建时可以使用"?"占位，运行时替换。PreparedStatement示例String cmd = "SELECT * FROM class WHERE student_id >= ?"; PreparedStatement ps = con.prepareStatement(cmd); ps.setInt(1, 3); ResultSet rs = ps.executeQuery(); while (rs.next()) { // do something... } rs.close(); ps.close();可以使用setXXX(idx, val)函数设置占位符"?"的值，XXX表示要设置的类型，例如Int，String，其中idx表示第几个占位符表示的位置，val表示要更改的值，注意，idx从1开始而不是从0开始！！！JDBC ResultSet结果集中有一个光标指向当前在结果的第几行，通过rs.next()将光标移动到下一行，如果不存在下一行则返回false。ResultSet中的方法beforeFirst() 光标移动到第一行之前afterLast() 光标移动到最后一行之后first() 光标移动到第一行last() 光标移动到最后一行absolute(int row) 光标移动到第row行relative(int row) 光标偏移row行，row可为负数previous() 光标移动到上一行int getRow() 获取当前光标位置getXXX(String s) 获取当前行字段为s的值，XXX为int，String等。JDBC 事务使用下面这行语句停止connection的自动提交conn.setAutoCommit(false);若要提交更改，使用conn.commit()回滚更改使用conn.rollback()示例try { conn.setAutoCommit(false); Statement stmt = conn.createStatement(); String SQL = "INSERT INTO students (stu_name, date)" + "VALUES ('a', NOW())"; stmt.executeUpdate(SQL); //Submit a malformed SQL statement that breaks SQL = "INSERT INTO students (stu_name, date)" + "VALUES ('b', NOW())"; stmt.executeUpdate(SQL); conn.commit(); } catch (SQLException e) { conn.rollback(); }也可以使用保存点Savepoint savepoint1 = conn.setSavepoint("Savepoint1"); conn.rollback(savepoint1);
MySQL基础连接MySQL通过Shell控制台连接MySQL[root@host]# mysql -u root -p - 也可以创建其他用户之后，使用其他用户登陆 - 推出mysql使用exit命令除此之外，Java可以通过JDBC连接到MySQL数据库MySQL基本操作sql语句是操作关系型数据库的一种语句，一般以分号结尾。sql语句不区分大小写创建数据库CREATE DATABASE 数据库名;使用mysqladmin创建数据库[root@host]# mysqladmin -u root -p create ABC Enter password:******以上语句创建了数据库ABC删除数据库drop database 数据库名;使用mysqladmin删除数据库[root@host]# mysqladmin -u root -p drop ABC Enter password:******删除了名为ABC的数据库注意⚠️一旦删除数据库，该数据库所有内容将消失且不可恢复选择数据库use ABC;以上语句选择了ABC数据库，意思是之后进行的操作将是对数据库ABC进行的操作MySQL数据类型MySQL支持很多种类型，主要可以分为：数值、字符串、日期三种类型数值类型整数类型分为很多种，TINYINT、SMALLINT、MEDIUMINT、INT或INTEGER、BIGINT分别占用1、2、3、4、8字节空间，且全部有有符号类型和无符号类型。浮点类型三种FLOAT占用4Bytes$(-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38)$DOUBLE占用8Bytes$(-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308)$DECIMAL精确浮点类型，对于DECIMAL(M,D)有M位有效数字，D位小数日期和时间DATE占用3Bytes范围$1000-01-01/9999-12-31$格式为年-月-日TIME占用3Bytes范围$-838:59:59/838:59:59$格式为小时:分钟:秒YEAR占用1Bytes范围$1901/2155$DATETIME占用8Bytes范围1000-01-01 00:00:00/9999-12-31 23:59:59TIMESTAMP占用4Bytes范围1970-01-01 00:00:00到格林威治时间2038年1月19日 凌晨 03:14:07时间戳字符串类型创建数据表CREATE TABLE table_name (column_name column_type);例CREATE TABLE IF NOT EXISTS student( student_id INT UNSIGNED AUTO_INCREMENT, student_name VARCHAR(100) NOT NULL, date DATE, PRIMARY KEY (student_id) )ENGINE=InnoDB DEFAULT CHARSET=utf8;注：AUTO_INCREMENT表示自增（每次会自动加一）一般用于主键如果不希望某一列为空，可以使用NOT NULLPRIMARY KEY (student_id)将student_id设置为主键ENGINE 设置存储引擎，CHARSET 设置字符编码。注意：在添加表之前，先使用use切换到想要操作的数据库中删除数据表DROP TABLE table_name;插入数据INSERT INTO table_name ( field1, field2,...fieldN ) VALUES ( value1, value2,...valueN );例INSERT INTO table_name (student_id, student_name, date) VALUES (1, "YorkWu", NOW());如果要插入的是字符串类型，需要使用双引号引起来。日期类型，可以使用类似“2022-04-11”的方法注入（包括引号）查询数据SELECT column_name,column_name FROM table_name [WHERE Clause] [LIMIT N] [OFFSET M]查询中可以选择一个或者多个表，用逗号隔开可以读取一条或多条记录可以使用星号*来代替其他字段，表示选择全部可以使用WHERE包含一些条件可以用LIMIT表示限制返回几条数据可以通过OFFSET设置开始查询的偏移量，默认为0WHERESELECT field1, field2,...fieldN FROM table_name1, table_name2... [WHERE condition1 [AND [OR]] condition2.....在WHERE后可以指定任何条件同样适用于DELETE或者UPDATE命令包含=,<,>,<>或!=,<=,>=运算符默认不区分大小写如果想要区分大小写，可以使用BINARY关键字例SELECT * from student WHERE stydent_name='YorkWu';//不区分大小写 SELECT * from student WHERE BINARY student_name='yorkwu';//区分大小写UPDATEUPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause]可以一次更新多个字段可以在WHERE中指定某些条件例UPDATE student SET student_name='wyy' WHERE runoob_id=1;DELETEDELETE FROM table_name [WHERE Clause]如果未使用WHERE指定，则相当于清空表LIKE我们知道，在使用更新，删除，等语句中，可以使用WHERE限定修改的条目，而WHERE只能做一些精确选择操作例如某种列为某个字符串（student_name="YorkWu"），但是有时候我们需要筛选例如所有含有某个字段的项（比如所有含有.com字段的文本）。就需要用到LIKE，在LIKE中%可以表示任何字符，类似于SELECT中*的作用，如果不使用%则与WHERE中的=没有本质区别SELECT field1, field2,...fieldN FROM table_name WHERE field1 LIKE condition1 [AND [OR]] filed2 = 'somevalue'LIKE可以用来代替=，也就是说，LIKE是用在WHERE内部的例SELECT * from student WHERE student_name LIKE '%Wu';则所有student_name中含有Wu字串的项都会被选中排序在MySQL中可以使用ORDER BY来以指定关键字排序SELECT field1, field2,...fieldN FROM table_name1, table_name2... ORDER BY field1 [ASC [DESC][默认 ASC]], [field2...] [ASC [DESC][默认 ASC]]可以设置多个字段排序使用ASC或者DESC来指定是升序排序或者降序排序，默认使用升序排列可以使用WHERE/LIKE设置条件SELECT * from student ORDER BY student_name ASC;分组GROUP BY语句根据一个或多个列对结果集进行分组在分组的列上可以使用count,sum,avg等函数SELECT column_name, function(column_name) FROM table_name WHERE column_name operator value GROUP BY column_name;function替换为count, sum,avg对于一张表+----+--------+---------------------+--------+ | id | name | date | signin | +----+--------+---------------------+--------+ | 1 | 小明 | 2016-04-22 15:25:33 | 1 | | 2 | 小王 | 2016-04-20 15:25:47 | 3 | | 3 | 小丽 | 2016-04-19 15:26:02 | 2 | | 4 | 小王 | 2016-04-07 15:26:14 | 4 | | 5 | 小明 | 2016-04-11 15:26:40 | 4 | | 6 | 小明 | 2016-04-04 15:26:54 | 2 | +----+--------+---------------------+--------+以姓名统计，并统计每个姓名出现次数SELECT name, COUNT(*) FROM employee_tbl GROUP BY name; +--------+----------+ | name | COUNT(*) | +--------+----------+ | 小明 | 3 | | 小王 | 2 | | 小丽 | 1 | +--------+----------+(with rollup)以姓名统计，统计之后计算每个人的signin之和，最后再计算总数SELECT name, SUM(signin) as signin_count FROM employee_tbl GROUP BY name WITH ROLLUP; +--------+--------------+ | name | signin_count | +--------+--------------+ | 小丽 | 2 | | 小明 | 7 | | 小王 | 7 | | NULL | 16 | +--------+--------------+对于NULL可以使用函数coalesce(a, b, c)修改，如果a为空则返回b，b若也为空返回cSELECT coalesce(name, 'tot'), SUM(signin) as signin_count FROM employee_tbl GROUP BY name WITH ROLLUP; +-----------------------+--------------+ | coalesce(name, 'tot') | signin_count | +-----------------------+--------------+ | 小丽 | 2 | | 小明 | 7 | | 小王 | 7 | | tot | 16 | +-----------------------+--------------+使用连接获取多个表之前已经学习了从一个表中获取数据，使用连接可以从多个表中获取数据。JOIN可以在SELECT, UPDATE, DELETE等语句中使用JOIN分为三类INNER JOIN（内连接、等值连接）：获取两个表中字段匹配关系LEFT JOIN（左连接）：获取左边表中所有记录，如果与右表有匹配，输出对应匹配的值RIGHT JOIN（右连接）：与左连接相反INNER JOIN使用INNER JOIN合并两个表具有相同关键字的记录mysql> select * from a; +------+-------+ | name | count | +------+-------+ | a | 1 | | b | 2 | | c | 3 | | d | 4 | +------+-------+ 4 rows in set (0.00 sec) mysql> select * from b; +----+------+---------------------+ | id | name | date | +----+------+---------------------+ | 1 | abc | 2022-04-15 19:36:34 | | 2 | a | 2022-04-07 19:36:51 | | 3 | c | 2022-04-27 19:37:04 | +----+------+---------------------+mysql> SELECT x.name, y.id, x.count, y.date FROM a x INNER JOIN b y ON x.name = y.name; +------+----+-------+---------------------+ | name | id | count | date | +------+----+-------+---------------------+ | a | 2 | 1 | 2022-04-07 19:36:51 | | c | 3 | 3 | 2022-04-27 19:37:04 | +------+----+-------+---------------------+ 2 rows in set (0.00 sec)LEFT JOIN读取左边表中所有数据，如果右表有对应数据则添加mysql> SELECT x.name, y.id, x.count, y.date FROM a x LEFT JOIN b y ON x.name = y.name; +------+------+-------+---------------------+ | name | id | count | date | +------+------+-------+---------------------+ | a | 2 | 1 | 2022-04-07 19:36:51 | | b | NULL | 2 | NULL | | c | 3 | 3 | 2022-04-27 19:37:04 | | d | NULL | 4 | NULL | +------+------+-------+---------------------+ 4 rows in set (0.00 sec)如上，会读取左表所有数据，其中不存在于右表的数据用NULL代替。RIGHT JOIN同LEFT JOIN相反mysql> SELECT x.name, y.id, x.count, y.date FROM a x RIGHT JOIN b y ON x.name = y.name; +------+----+-------+---------------------+ | name | id | count | date | +------+----+-------+---------------------+ | NULL | 1 | NULL | 2022-04-15 19:36:34 | | a | 2 | 1 | 2022-04-07 19:36:51 | | c | 3 | 3 | 2022-04-27 19:37:04 | +------+----+-------+---------------------+ 3 rows in set (0.00 sec)对于NULL的处理对于NULL，在WHERE中发现无法使用=或者!=符号判断，将其改为IS NULL和IS NOT NULL即可例如mysql> SELECT x.name, y.id, x.count, y.date FROM a x RIGHT JOIN b y ON x.name = y.name WHERE x.name IS NULL; +------+----+-------+---------------------+ | name | id | count | date | +------+----+-------+---------------------+ | NULL | 1 | NULL | 2022-04-15 19:36:34 | +------+----+-------+---------------------+ 1 row in set (0.00 sec) mysql> SELECT x.name, y.id, x.count, y.date FROM a x RIGHT JOIN b y ON x.name = y.name WHERE x.count IS NOT NULL; +------+----+-------+---------------------+ | name | id | count | date | +------+----+-------+---------------------+ | a | 2 | 1 | 2022-04-07 19:36:51 | | c | 3 | 3 | 2022-04-27 19:37:04 | +------+----+-------+---------------------+ 2 rows in set (0.00 sec)MySQL 事务事务；在一个事务中可以包含多个语句，且统一执行，如果成功则全部成功，如果失败则全部失败。一般只有使用InnoDB数据库引擎才支持事务有时候多个操作同时成功才有意义，如果一些成功，一些失败，不但不能成功反而会扰乱数据库的数据。事务需要满足的四个条件（四个性质）：原子性：不可分割性，表示事务不可进行分割。一致性：在事务开始前和执行后，数据库没有被破坏。即进行的操作符合预期。因此此性质需要由操作人保证，事物本身不能保证此性质。隔离性：多个并发事务同时执行的情况下执行正确的能力。数据库允许并发，隔离性用于防止多个事务交叉执行（顺序不一定）导致数据不一致的问题。事务隔离分为不同级别：包括读未提交，读提交，可重复读，和可串行化。在并发的情况下，由于多个进程同时对一个数据进行读写操作，可能出现Dirty Read（赃读），Non-Repeatable Read（不可重复读），Phantom（幻读）三种错误情况。赃读：在事务中读到了其他线程未提交的数据不可重复读：在事务中读取两次，前后的数据不一样幻读：在两次连续查找操作中，前后查找结果不一样（注意：幻读与不可重复读类似，但幻读更偏向于增加和删除，而不可重复读偏向于修改）对于读未提交级别，对本线程尚未提交的事务可以读到其他未被提交的事务修改的数据，则会发生Dirty Read；对于读提交级别，对本线程未提交的事务，只能读到其他已被提交的事务，但可能会发生Non-Repeatable Read；对于可重复读级别，只有本线程提交时，才能读到其他已经提交的事务修改的数据，但有可能在未提交时读到其他线程已提交的新增或者删除操作，因此可能出现幻读（Phantom）。对于可串行化，不会出现上述三种读写异常。一般，使用可重复读级别，也是默认级别，其相对稳定且对数据库效率影响较小。持久性：一旦提交事务，对数据的修改就是永久的，即使断电也不会丢失（保存在海量存储器中）事务控制语句默认每一句话都提交一遍（隐式提交）BEGIN显示地开启一个事务COMMIT显示地提交一个事务ROLLBACK回滚一个事务（回滚到BEGIN）SAVEPOINT xxx新建一个保存点xxx，允许存在多个保存点RELEASE SAVEPOINT xxx删除名为xxx的保存点ROLLBACK TO xxx回滚到名为xxx的保存点SET TRANSACTION设置事务的隔离级别
Servlet基础什么是ServletServlet是一种规范，对于B/S结构，需要一个Server处理由Browser发送的请求，Servlet是一个处理这些请求的模块，通过Servlet可以处理浏览器的请求并且不涉及底层源码。Servlet提供了一系列规范，这样使得所有遵循Servlet规范的webapp都可以在不同的服务器运行，例如Tomcat。规范了哪些接口规范了哪些类规范了一个web应用中应该有哪些配置文件规范了一个web应用中配置文件的名字规范了一个web应用中配置文件存放的路径规范了一个web应用中配置文件的内容规范了一个合法有效的web应用它的目录结构应该是怎样的。.....符合Servlet规范的webapp开发步骤第一步：在webapps目录下新建一个目录，起名crm（这个crm就是webapp的名字）。注意：crm就是这个webapp的根第二步：在webapp的根下新建一个目录：WEB-INF注意：全部大写。第三步：在WEB-INF目录下新建一个目录：classes这个目录下存放的是Java程序编译之后的class文件。第四步：在WEB-INF目录下新建一个目录：lib注意：这个目录不是必须的。但如果一个webapp需要第三方的jar包的话，这个jar包要放到这个lib目录下，这个目录的名字也不能随意编写，必须是全部小写的lib。例如java语言连接数据库需要数据库的驱动jar包。那么这个jar包就一定要放到lib目录下。第五步：在WEB-INF目录下新建一个文件：web.xml<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd" version="5.0" metadata-complete="true"> <servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.bjpowernode.servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>目录结构webapproot |------WEB-INF |------classes(存放字节码) |------lib(第三方jar包) |------web.xml(注册Servlet) |------html |------css |------javascript |------image ....Servlet示例import jakarta.servlet.*; import java.io.IOException; //实现Servlet接口 public class AServlet implements Servlet { // init为初始化 // init方法只执行一次 // 在AServlet对象第一次被创建之后执行。 // init方法通常是完成初始化操作的。 // init方法在执行的时候AServlet对象已经被创建出来了。 @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("AServlet's init method execute!"); } // service方法：是处理用户请求的核心方法。 // 只要用户发送一次请求，service方法必然会执行一次。 @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("AServlet's service method execute!"); } // destroy方法也是只执行一次。 // Tomcat服务器在销毁AServlet对象之前会调用一次destroy方法 // destroy方法在执行的时候，AServlet对象的内存还没有被销毁。即将被销毁。 // destroy方法中可以编写销毁前的准备。 // 比如说，服务器关闭的时候，AServlet对象开启了一些资源，这些资源可能是流，可能是数据库连接 // 那么，关闭服务器的时候，要关闭这些流，关闭这些数据库连接，那么这些关闭资源的代码就可以写到destroy方法当中。 @Override public void destroy() { System.out.println("AServlet's destroy method execute!"); } @Override public ServletConfig getServletConfig() { return null; } @Override public String getServletInfo() { return null; } }Servlet的生命周期Servlet对象的生命周期在第一次被用户访问时创建在服务器关闭时销毁在整个webapp运行中，Servlet对象只有一个（单例）Servlet对象的维护Servlet对象的创建，方法调用，销毁均由服务器控制生命周期也由Web容器控制启动服务器时，Servlet对象并没有被创建如果希望在启动时创建Servlet对象，可以在web.xml加入参数\<load-on-startup>该标签中填写整数，数字越小，优先级越高<servlet> <servlet-name>aservlet</servlet-name> <servlet-class>com.bjpowernode.javaweb.servlet.AServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>aservlet</servlet-name> <url-pattern>/a</url-pattern> </servlet-mapping>Servlet生命周期服务器启动，Servlet对象未创建用户访问对应url，创建Servlet对象并调用init方法。之后调用service方法相应请求。之后每次用户访问，只调用service方法响应请求，因此有几次请求就会调用多少次service方法服务器关闭前，执行destroy方法，销毁Servlet对象。注：不建议手动定义Servlet对象的构造函数，Tomcat服务器使用反射机制创建Servlet对象，如若重写构造函数造成无参构造函数消失，创建对象时会导致服务器内部错误（500错误）需要长久保存的信息，在destroy中重写GenericServlet直接编写Servlet，比较复杂有时候init，destroy方法不一定需要，但是却必须重写，导致大量无用代码生成。构建一个GenericServlet（通用Servlet）抽象类，其中重写所有Servlet接口的方法，并将service方法设置为abstract我们需要使用Servlet的时候可以继承GenericServlet，减少重写的代码量同时必须继承service方法实现用户逻辑例import jakarta.servlet.*; import java.io.IOException; public abstract class GenericServlet implements Servlet { // 成员变量 private ServletConfig config; /** * init方法中的ServletConfig对象由服务器创建。 */ @Override public final void init(ServletConfig config) throws ServletException { this.config = config; // 调用init()方法 this.init(); } /** * 这个init方法是供子类重写的。 */ public void init(){ } @Override public ServletConfig getServletConfig() { return config; } /** * 抽象方法，这个方法最常用。所以要求子类必须实现service方法。 * @param servletRequest * @param servletResponse * @throws ServletException * @throws IOException */ public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException; @Override public String getServletInfo() { return null; } @Override public void destroy() { } }一般称这种设计模式为适配器设计模式ServletConfigServlet的配置信息对象存储了web.xml中的配置信息一个Servlet对应一个ServletConfig对象在Servlet的init函数调用时会传入一个ServletConfig对象ServletConfig常用方法public String getInitParameter(String name); // 通过初始化参数的name获取value public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name public ServletContext getServletContext(); // 获取ServletContext对象 public String getServletName(); // 获取Servlet的name注意：以上方法在GenericServlet中可以直接使用this调用，因为GenericServlet实现了ServletConfig接口。ServletContextServlet的环境（上下文）对象，在一个webapp中只有一个，也就是说不管几个Servlet对象，它们的ServletContext是共用的。ServletContext在服务器启动时创建，服务器关闭时终止。常用方法public String getInitParameter(String name); // 通过初始化参数的name获取value public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name其参数存放在web.xml文件当中 <context-param> <param-name>pageSize</param-name> <param-value>10</param-value> </context-param> <context-param> <param-name>startIndex</param-name> <param-value>0</param-value> </context-param> // 获取应用的根路径 public String getContextPath(); // 获取文件的绝对路径（真实路径） public String getRealPath(String path); // 通过ServletContext对象记录日志 public void log(String message); public void log(String message, Throwable t); // 这些日志信息记录到 // localhost.2022-04-02.logServletContext属于应用域，与其对应的还有请求域，会话域等，在不同的域可以存放一些数据，在对应的域之内均可使用，例如在应用域存放的数据在整个webapp中均可使用。// 向ServletContext应用域中存数据 public void setAttribute(String name, Object value); // map.put(k, v) // 从ServletContext应用域中取数据 public Object getAttribute(String name); // Object v = map.get(k) // 删除ServletContext应用域中的数据 public void removeAttribute(String name); // map.remove(k)HTTP协议、HttpServlet在实际web开发中，通常不继承GenericServlet而继承HTTPServlet，HTTPServlet也是抽象类，其继承于GenericServlet，并且专门为HTTP协议的请求做了一些调整和优化。HTTP协议HTTP协议：是W3C制定的一种超文本传输协议。包括请求协议于响应协议请求协议其包括四部分：请求行请求头空白行请求体HTTP请求样例报文：GET:GET /servlet05/getServlet?username=wyy&userpwd=12345678 HTTP/1.1 请求行 Host: localhost:8080 请求头 Connection: keep-alive sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/servlet05/index.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 空白行 请求体POST:POST /servlet05/postServlet HTTP/1.1 请求行 Host: localhost:8080 请求头 Connection: keep-alive Content-Length: 25 Cache-Control: max-age=0 sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 Origin: http://localhost:8080 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/servlet05/index.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 空白行 username=wyy&userpwd=12345678 请求体样例分析请求行请求类型getpostdeleteputheadoptionstrace常用get和postURI统一资源标识符。代表网络中某个资源的名字。如：/servlet05/postServletHTTP协议版本表示本请求使用的HTTP协议版本请求头请求的主机:端口浏览器身份信息Cookie语言编码方式……请求体请求包含的具体数据响应协议也包括四部分状态行响应头空白行响应体响应报文示例：HTTP/1.1 200 ok 状态行 Content-Type: text/html;charset=UTF-8 响应头 Content-Length: 160 Date: Mon, 08 Nov 2021 13:19:32 GMT Keep-Alive: timeout=20 Connection: keep-alive 空白行 响应体 from get servlet from get servlet 样例分析状态行协议版本状态码200表示请求响应成功404找不到目标资源405请求类型不符（前端get请求后端只能处理post请求）500服务器内部错误状态描述信息对前边状态码的解释例如OK表示成功404 NotFound响应头响应的内容类型响应的编码格式，语言响应时间是否长期存在……响应体响应返回的具体内容HttpServletHttpServlet类是专门为HTTP协议准备的。比GenericServlet更加适合HTTP协议下的开发。jakarta.servlet.http.HttpServlet- 旧版本使用javax.servlet.http.HttpServlet http包下有- jakarta.servlet.http.HttpServlet （HTTP协议专用的Servlet类，抽象类） - jakarta.servlet.http.HttpServletRequest （HTTP协议专用的请求对象） - jakarta.servlet.http.HttpServletResponse （HTTP协议专用的响应对象） HttpServletRequest对象中封装了什么信息？- HttpServletRequest对象。 - HttpServletRequest中封装了请求协议的全部内容。 - Tomcat服务器（WEB服务器）将“请求协议”中的数据全部解析出来，并将这些数据全部封装到request对象当中了。 - 也就是说，我们只要使用HttpServletRequest，就可以获取请求协议中的数据。 HttpServlet源码 // HttpServlet类 public abstract class HttpServlet extends GenericServlet { @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { // 将ServletRequest和ServletResponse向下转型为HttpServletRequest和HttpServletResponse request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException(lStrings.getString("http.non_http")); } // 调用重载的service方法。 service(request, response); } // 这个service方法的两个参数都是带有Http的。 // 这个service是一个模板方法。 // 在该方法中定义核心算法骨架，具体的实现步骤到子类中去完成。 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求方式 // 这个请求方式最终可能是："" // 注意：request.getMethod()方法获取的是请求方式，可能是七种之一： // GET POST PUT DELETE HEAD OPTIONS TRACE String method = req.getMethod(); // 如果请求方式是GET请求，则执行doGet方法。 if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { // 如果请求方式是POST请求，则执行doPost方法。 doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ // 报405错误 String msg = lStrings.getString("http.method_get_not_supported"); sendMethodNotAllowed(req, resp, msg); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 报405错误 String msg = lStrings.getString("http.method_post_not_supported"); sendMethodNotAllowed(req, resp, msg); } } /* 通过以上源代码分析： 假设前端发送的请求是get请求，后端程序员重写的方法是doPost 假设前端发送的请求是post请求，后端程序员重写的方法是doGet 会导致：发生405错误。405表示前端的错误，发送的请求方式不对。和服务器不一致。不是服务器需要的请求方式。 通过以上源代码可以知道：只要HttpServlet类中的doGet方法或doPost方法执行了，必然405. 怎么避免405的错误呢？ 后端重写了doGet方法，前端一定要发get请求。 后端重写了doPost方法，前端一定要发post请求。 这样可以避免405错误。 不建议同时重写doGet和doPost，该报错就让它报错更容易找到错误 */最好不要直接重写service方法，否则无法触发405错误最终Servlet应用开发方法：创建一个Servlet应用类继承HttpServlet。重写doGet或者doPost将Servlet配置到web.xml文件中或者使用注解配置编写或者导入相应的前端页面，进行测试HttpServletRequest接口，继承于ServletRequest对于Tomcat服务器，org.apache.catalina.connector.RequestFacade 实现了 HttpServletRequest接口HttpServletRequest中的信息包括用户请求的HTTP报文中的所有信息生命周期Request和Responce分别是请求与返回对象只在当前请求有效属于请求域常用方法获取用户提交的数据Map<String,String[]> getParameterMap() 获取键值对Map Enumeration<String> getParameterNames() 获取Map集合中所有的key String[] getParameterValues(String name) 根据key获取Map集合的value String getParameter(String name) 获取这个键对应的第一个元素。注意，如果前端提交了数字，则提交时会变为字符串，也就是后端接收到的永远是字符串。请求域相关方法HttpServletRequest属于请求域，可以通过它对请求域的数据进行操作请求域只在一次请求范围内有效，相比于应用域，其作用域要小得多一次请求之后，这个请求域将会销毁相关方法void setAttribute(String name, Object obj); // 向域当中绑定数据。 Object getAttribute(String name); // 从域当中根据name获取数据。 void removeAttribute(String name); // 将域当中绑定的数据移除在对请求域和应用域选择时，尽量选择作用域小的。转发// 第一步：获取请求转发器对象 RequestDispatcher dispatcher = request.getRequestDispatcher("/b"); // 第二步：调用转发器的forward方法完成跳转/转发 - 因此在两个Servlet之间传递信息时，可以直接将数据放到应用域，但不建议，因为应用域作用范围较大，不安全，效率低。更好的方法是将信息提交到请求域之后再进行转发。 - 注意，转发不一定要转发到一个Servlet对象，也可以转发到一切web应用例如html，jsp…… - 注意，转发的路径必须以“/”开始，无应用名其他常用方法// 获取客户端的IP地址 String remoteAddr = request.getRemoteAddr(); // POST请求 // Tomcat10之后，request请求体当中的字符集默认就是UTF-8，不需要设置字符集，不会出现乱码问题。 // Tomcat9前（包括9在内），如果出现乱码，执行以下代码。 request.setCharacterEncoding("UTF-8"); // 在Tomcat9之前（包括9），响应中文 response.setContentType("text/html;charset=UTF-8"); // get请求乱码问题 // get请求发送的时候，数据是在请求行上提交的，不是在请求体当中提交的。 // 方案：修改CATALINA_HOME/conf/server.xml配置文件 <Connector URIEncoding="UTF-8" /> // 注意：从Tomcat8之后，URIEncoding的默认值就是UTF-8，所以GET请求也没有乱码问题了。 // 获取应用的根路径 String contextPath = request.getContextPath(); // 获取请求方式 String method = request.getMethod(); // 获取请求的URI String uri = request.getRequestURI(); // /aaa/testRequest // 获取servlet path ## 一些注意事项 ### Web首页的配置对于直接访问应用名，未指定资源名时，应该显示网站首页，例如访问http://localhost:8080/servlet并未指定要访问那个文件，此时应该显示http://localhost:8080/servlet/index.html对于首页的配置在web.xml中配置<welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list>可以设置多个首页页面<welcome-file-list> <welcome-file>abc/a.html</welcome-file> <welcome-file>index.html</welcome-file> </welcome-file-list>默认使用上面的，找不到顺序向下找注意：如果不配置，Tomcat服务器就会以index.html index.htm index.jsp作为一个web站点的首页。首页也可以是一个Servlet应用WEB-INF的访问权限WEB-INF中的文件不能被访问，静态资源必须放在其目录之外转发与重定向在一个web应用中有两种方法跳转资源转发重定向转发和重定向的代码转发// 获取请求转发器对象 RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list"); // 调用请求转发器对象的forward方法完成转发 dispatcher.forward(request, response); // 合并一行代码 request.getRequestDispatcher("/dept/list").forward(request, response);重定向// 注意：路径上要加一个项目名。 // 浏览器发送请求，请求路径上是需要添加项目名的。 // 以下这一行代码会将请求路径“/oa/dept/list”发送给浏览器 // 浏览器会自发的向服务器发送一次全新的请求：/oa/dept/list response.sendRedirect("/oa/dept/list");转发与重定向的区别转发是在服务器内部进行操作，是一次请求，浏览器不需要额外操作重定向是把要跳转的路径返回给浏览器由浏览器再次访问是两次请求使用转发与重定向的判定如果在原请求的Servlet中向请求域绑定了数据，则使用转发，同时将绑定的数据同步给新的资源除此之外，均使用重定向。因此重定向使用较多注：跳转的下一个资源不一定是Servlet转发可能存在刷新时的一些问题（？Servlet注解代替xml配置在之前的开发中，每一个Servlet应用都需要在web.xml中配置，导致开发繁琐易出错，可能有时需要不停切换java源码与配置文件Servlet3.0+引入了注解开发使用注解开发，可以减小开发难度，增加开发效率并不是说使用注解开发完全放弃xml配置文件，因为有时有些参数或者需要改变的数据，在配置文件中方便修改，避免调试或维护过程频繁修改代码。jakarta.servlet.annotation.WebServlet在Servlet类上使用@WebServlet注解，它具有以下参数。name：以指定Servlet的名字，等价于urlPartterns：指定映射的路径，可以指定多个字符串。loadOnStartUp：指定初始化等级注：使用时只需填写需要的，不需要全部填写。例package com.bjpowernode.javaweb.servlet; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; //@WebServlet(urlPatterns = {"/welcome1", "/welcome2"}) // 注意：当注解的属性是一个数组，并且数组中只有一个元素，大括号可以省略。 //@WebServlet(urlPatterns = "/welcome") // 这个value属性和urlPatterns属性一致，都是用来指定Servlet的映射路径的。 //@WebServlet(value = {"/welcome1", "/welcome2"}) // 如果注解的属性名是value的话，属性名也是可以省略的。 //@WebServlet(value = "/welcome1") //@WebServlet({"/wel", "/abc", "/def"}) @WebServlet("/wel") public class WelcomeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print("欢迎学习Servlet。"); } }Session（会话机制）Session机制什么是Session一个用户打开浏览器在一个网站完成很多操作，直到关闭浏览器，叫做一个会话一个会话包含多次请求Session对应Servlet包HttpSessionjarkata.servlet.http.HttpSessionSession的作用保存用户状态。由于HTTP是无状态协议（每次请求是独立的），需要一个机制来识别哪些请求来自同一个用户，例如登录一次后，在浏览器开启状态下继续使用无需重新登陆。为什么不设计成有状态协议有可能连接的用户数量非常多，无状态方便节省资源，不需要始终保持一个连接。会话域之前已经知道了请求域，应用域。会话域比请求域更广但没有应用域广会话域的范围在一个会话之内，也就是一个用户之内如果直接使用应用域存储用户信息，会造成无法分辨用户，及数据不安全的情况。Session对象的使用在请求中，得到会话对象HttpSession session = request.getSession();注：不同的人请求返回的session对象不同，同一个人请求返回的对象相同。常用方法// 获取key为name的对象 session.getAttribute(String name); // 获取keys，返回枚举（Enums） session.getAttributeNames(); // 设置一个数据 session.setAttribute(String s, Object o); // 删除名为s的数据 session.removeAttribute(String s);请求域，会话域，应用域使用原则尽量使用作用范围小的域。例：在用户访问某个需要登录的网页时，判断其Session中是否有数据，没有就重定向到登录页面，有就正常显示。登陆成功之后将用户的登录信息添加到Session域销毁Session对象在登出或者过期的时候可以选择销毁Session对象session.invalidate();Session的原理浏览器在内存中以Cookie形式保存了JSESSIONID=xxxxxx...xxxx，当浏览器关闭时，这个SessionId就没有了。Session的池是一个Map，其中key是sessionid，value是session对象在浏览器初次访问网页时，服务器自动生成session对象同时生成id并随响应返回给浏览器在之后的访问中，服务器自动将sessionid随请求发送给服务器，服务器通过sessionid取出该用户对应的session对象以识别该用户身份。若浏览器禁用Cookie，session机制依旧可以通过url重写机制实现，但需要在每个请求路径后加一个sessionid，大大增加项目开发复杂度，所以一般若禁用cookie就无法使用session机制Cookiecookie最终是保存在浏览器客户端上的。可以保存在运行内存中。（浏览器只要关闭cookie就消失了。）也可以保存在硬盘文件中。（永久保存。）cookie的作用cookie和session机制其实都是为了保存会话的状态。cookie是将会话的状态保存在浏览器客户端上。（cookie数据存储在浏览器客户端上的。）session是将会话的状态保存在服务器端上。（session对象是存储在服务器上。）案例在淘宝升级之前，选购进购物车的商品，即使没有登录，在关闭浏览器再打开后仍然存在。将购物车中的商品编号放到cookie当中，cookie保存在硬盘文件当中。这样即使关闭浏览器。硬盘上的cookie还在。下一次再打开的时候，查看购物车的时候，会自动读取本地硬盘中存储的cookie，拿到商品编号，动态展示购物车中的商品。注意：cookie如果清除掉，购物车中的商品就消失了。十天内免登陆用户输入正确的用户名和密码，并且同时选择十天内免登录。登录成功后。浏览器客户端会保存一个cookie，这个cookie中保存了用户名和密码等信息，这个cookie是保存在硬盘文件当中的，十天有效。在十天内用户再次访问的时候，浏览器自动提交cookie给服务器，服务器接收到cookie之后，获取用户名和密码，验证，通过之后，自动登录成功。怎么让cookie失效？设置cookie期限在客户端浏览器上手动清除cookie改密码HTTP协议中规定：任何一个cookie都是由name和value组成的。name和value都是字符串类型的。当浏览器发送请求的时候，会自动携带该路径下的cookie数据给服务器。Servlet中使用Cookiejakarta.servlet.http.Cookie;常用方法cookie.setMaxAge(60 * 60); // 设置cookie在一小时之后失效。 cookie.setPath(“/servlet13”); //表示只要是这个路径及其子路径，都会提交这个cookie给服务器 // 接收cookie - Cookie的有效时间 - 不设置则浏览器关闭时消失 - 设置为0：删除该Cookie和同名Cookie - 设置为>0，一定会在浏览器关闭时保存在硬盘中 - 设置为<0，和不设置一样 - Cookie的默认路径 - 假设现在发送的请求路径是“/servlet13/cookie/generate”生成的cookie，如果cookie没有设置path - 默认的path是：/servlet13/cookie 以及它的子路径。 - 也就是说，以后只要浏览器的请求路径是/servlet13/cookie 这个路径以及这个路径下的子路径，cookie都会被发送到服务器。
JavaIO流用于数据的输入输出，所有输入流都是InputStream（数据输入）或Reader（文本输入）的子类，输出流都是OutputStream（数据输出）或Writer（文本输出）的子类。File类File类处理文件的基本信息例如文件所在目录，文件长度或文件读写权限等，不进行对文件的读写。File类构造函数：File(String filename);File(String directoryPath, String filename);File(File dir, String filename);filename为文件名，directoryPath是文件路径，dir为一个目录。默认第一种要求该文件与当前在同一路径下。文件属性public String getName():获取文件名public boolean canRead():返回文件是否可读public boolean canWrite():~可写public boolean exists()判断文件是否存在public long length()获取文件长度public String getParent()获取文件父目录public boolean isFile()判断文件是否为文件（不是目录）public boolean isDirectory()判断是否为一个目录public boolean isHidden()判断是否为隐藏文件public long lastModified()获取最后被修改的时间（时间戳）目录创建目录：public boolean mkdir()创建成功返回true，失败或已存在返回false列出目录下文件public String[] list():列出目录下所有文件public File[] listFiles(): 用File对象返回所有文件列出目录下指定类型的文件public String[] list(FilenameFilter obj);public File[] listFiles(FilenameFilter obj);其中，FilenameFilter为一个接口，其中有一accept方法public boolean accept(File dir, String name);对于一个文件，File对象将调用obj的accept方法，当accept返回为True则会将该文件添加到返回的数组中。例：// Example10_2.java(Demo) import java.io.*; public class Example10_2 { public static void main(String args[]) { File dirFile = new File("."); FileAccept fileAccept = new FileAccept(); fileAccept.setExtendName("java"); String fileName[] = dirFile.list(fileAccept); for(String name:fileName) { System.out.println(name); } } } //FileAccept.java(Impl) import java.io.*; public class FileAccept implements FilenameFilter { private String extendName; public void setExtendName(String s) { extendName="."+s; } public boolean accept(File dir,String name) { //重写接口中的方法 return name.endsWith(extendName); } }文件的创建与删除当创建一个文件对象后，若文件不存在可以使用方法创建一个文件，也可以用方法删除一个文件。public boolean creatNewFile():若不存在文件则将创建一个public boolean delete();若存在该文件将删除当前文件运行可执行文件使用Runtime类声明一个对象，然后使用getRuntime得到一个对象，之后可以使用exec(String command)执行一个命令（command为命令）。例：import java.io.*; public class Example10_3 { public static void main(String args[]) { try{ Runtime ce=Runtime.getRuntime(); File file=new File("c:/windows","Notepad.exe"); ce.exec(file.getAbsolutePath()); file=new File("C:\\Program Files\\Internet Explorer","IEXPLORE www.sina.com "); ce.exec(file.getAbsolutePath()); } catch(Exception e) { System.out.println(e); } } }文件字节输入输出流输入FileInputStream进行文件输入的类构造方法FileInputStream(String name);FileInputStream(File file);其打开一个指向文件的通道，创建文件流时，可能产生异常，例如文件不存在，或者IO错误，因此需要使用try-catch捕获异常try { FileInputStream in = new FileInputStream("hello.txt"); } catch(IOException e) { System.out.println("File read error:"+e); }使用输入流读取字节可以使用read()顺序读取文件，直到达到文件尾int read()以字节为单位读取单个字节，返回整数（0~255），若读到文件尾，返回-1int read(byte[] b)尝试读取b.length个字节到数组中，返回实际读取的字节数目，如果到达文件尾返回-1int read(byte[] b, int off, int len)尝试读取len个字节到b中，off指定从b的某个位置开始存放读取的数据关闭流使用close()方法关闭流。输出流构造方法除与输入流类似的构造方法外其后可以增加一个boolean类型变量表示是否对文件追加。FileOutputStream(String name, boolean append):append为true则在源文件基础上追加为false则覆盖源文件FileOutputStream(String name, boolean append):同理使用输出流写入字节void write(int n):写入单个字节void write(byte[] b):写入一个字节数组void write(byte[] b, int off, int len):给定字节数组中起始与off取len个字节写入void close():关闭字节流关闭流在使用close()方法关闭后，可以保证缓冲区被写入目的地，若不关闭可能没有写入磁盘例输出：import java.io.*; public class Example10_5 { public static void main(String args[]) { byte [] a = "新年快乐".getBytes(); byte [] b = "Happy New Year".getBytes(); File file = new File("a.txt"); //输出的目的地 try{ OutputStream out=new FileOutputStream(file); //指向目的地的输出流 System.out.println(file.getName()+"的大小:"+file.length()+"字节");//a.txt的大小:0字节 out.write(a); //向目的地写数据 out.close(); out=new FileOutputStream(file,true); //准备向文件尾加内容 System.out.println(file.getName()+"的大小:"+file.length()+"字节");///a.txt的大小:8字节 out.write(b,0,b.length); System.out.println(file.getName()+"的大小:"+file.length()+"字节");///a.txt的大小:8字节 out.close(); } catch(IOException e) { System.out.println("Error "+e); } } }输入：import java.io.*; public class Example10_4 { public static void main(String args[]) { int n=-1; byte [] a=new byte[100]; try{ File f=new File("Example10_4.java"); InputStream in = new FileInputStream(f); while((n=in.read(a,0,100))!=-1) { String s=new String (a,0,n); System.out.print(s); } in.close(); } catch(IOException e) { System.out.println("File read Error"+e); } } }文件字符输入输出流与FileInputStream和FileOutputStream对应的分别是FileReader和FileWriter，分别为Reader和Writer的子类。构造方法有FileReader(String name);FileReader(File file);FileWriter(String name);FileWriter(File file);FileWriter(String name, boolean append);FileWriter(File file, boolean append);其他方法与字节输入输出相同对与FileWriter，执行flush()方法可以直接将缓冲区内容写入磁盘。缓冲流比FileReader和FileWriter更加高级的流支持更多的操作构造方法BufferReader(Reader in);BufferWriter(Writer out);常用方法String readLine()读取下一行对与BufferWriter，newLine向文件写入一个换行符示例import java.io.*; import java.util.*; public class Example10_7 { public static void main(String args[]) { File fRead = new File("english.txt"); File fWrite = new File("englishCount.txt"); try{ Writer out = new FileWriter(fWrite); BufferedWriter bufferWrite = new BufferedWriter(out); Reader in = new FileReader(fRead); BufferedReader bufferRead =new BufferedReader(in); String str = null; while((str=bufferRead.readLine())!=null) { StringTokenizer fenxi = new StringTokenizer(str); int count=fenxi.countTokens(); str = str+" 句子中单词个数:"+count; bufferWrite.write(str); bufferWrite.newLine(); } bufferWrite.close(); out.close(); in = new FileReader(fWrite); bufferRead =new BufferedReader(in); String s=null; System.out.println(fWrite.getName()+"内容:"); while((s=bufferRead.readLine())!=null) { System.out.println(s); } bufferRead.close(); in.close(); } catch(IOException e) { System.out.println(e.toString()); } } } //english.txt: /* The arrow missed the target. They rejected the union demand. Where does this road go to? Where does this road go to? */随机流RandomAccessFile类支持读写文件，构造方法RandomAccessFile(String name, String mode);name指定文件，mode取“r”，“rw”决定该对象对文件的操作类型。RandomAccessFile(File file, String mode);注意：RandomAccessFile流指向文件的时候其不会刷新缓存区。其中有一个方法用来seek(long a)用来定位RandomAccessFile流的读写位置，其中参数a确定要读写的文件光标距离文件开头的位置，使用方法getFilePointer()获取流的当前读写位置。RandomAccessFile对文件的读写更灵活。import java.io.*; public class Example10_8 { public static void main(String args[]) { RandomAccessFile inAndOut=null; int data[]={1,2,3,4,5,6,7,8,9,10}; try{ inAndOut=new RandomAccessFile("tom.dat","rw"); for(int i=0;i<data.length;i++) { inAndOut.writeInt(data[i]); } for(long i=data.length-1;i>=0;i--) { //一个int型数据占4个字节，inAndOut从 inAndOut.seek(i*4); //文件的第36个字节读取最后面的一个整数， System.out.printf("\t%d",inAndOut.readInt()); //每隔4个字节往前读取一个整数 } inAndOut.close(); } catch(IOException e){} } }对象流ObjectInputStream和ObjectOutputStream分别是InputStream和OutputStream类的子类，可以将一个对象读取或者写入到一个文件。构造方法ObjectInputStream(InputStream in)ObjectOutputStream(OutputStream out)由于两个实例都属于流，应当先创建对文件的输入输出流再进行构造。使用对象输入输出流对对象操作时，要保证对象是序列化的，且其中成员变量也应当是序列化的。Serializable接口中没有方法，因此不需要重写。例Demo:import java.io.*; public class Example10_13 { public static void main(String args[]) { TV changhong = new TV(); changhong.setName("长虹电视"); changhong.setPrice(5678); File file=new File("television.txt"); try{ FileOutputStream fileOut=new FileOutputStream(file); ObjectOutputStream objectOut=new ObjectOutputStream(fileOut); objectOut.writeObject(changhong); objectOut.close(); FileInputStream fileIn=new FileInputStream(file); ObjectInputStream objectIn=new ObjectInputStream(fileIn); TV xinfei=(TV)objectIn.readObject(); objectIn.close(); xinfei.setName("新飞电视"); xinfei.setPrice(6666); System.out.println("changhong的名字:"+changhong.getName()); System.out.println("changhong的价格:"+changhong.getPrice()); System.out.println("xinfei的名字:"+xinfei.getName()); System.out.println("xinfei的价格:"+xinfei.getPrice()); } catch(ClassNotFoundException event) { System.out.println("不能读出对象"); } catch(IOException event) { System.out.println(event); } } }TV:import java.io.*; public class TV implements Serializable{ String name; int price; public void setName(String s) { name=s; } public void setPrice(int n) { price=n; } public String getName() { return name; } public int getPrice() { return price; } }序列化与对象克隆一般两个对象如果具有相同的引用则具有相同的实体和功能。有时候我们希望得到一个对象的拷贝，很明显通过上述方法从文件中读入一个对象就是源对象的拷贝。若希望快速得到一个拷贝可以写入时写入内存而不是硬盘。可以将FileInputStream fileIn = new FileInputStream(file); ObjectInputStream objectIn = new ObjectInputStream(fileIn); FileOutputStream fileOut = new FileOutputStream(file); ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);改为ByteArrayInputStream inByte = new ByteArrayInputStream(file); ObjectInputStream objectIn = new ObjectInputStream(inByte); ByteArrayOutputStream outByte = new ByteArrayOutputStream(file); ObjectOutputStream objectOut = new ObjectOutputStream(outByte);即可使用Scanner解析文件对于Scanner，先把整个文件读入内存，再进行处理，优点是速度快，但是耗空间。创建Scanner对象File file = new File("hello.java"); Scanner input = new Scanner(file);input以空格、回车作为标记，调用next()返回下一个元素，hasNext()返回是否还存在下一个元素……使用正则表达式创建Scanner对象File file = new File("Hello.txt"); Scanner input = new Scanner(file); input.useDelimiter(正则表达式);例：//student.txt //张三的成绩是72分，李四成绩是69分，刘小林的成绩是95分。 //Java import java.io.*; import java.util.*; public class Example10_16 { public static void main(String args[]) { File file = new File("student.txt"); Scanner sc=null; int count=0; double sum=0; try { double score=0; sc = new Scanner(file); sc.useDelimiter("[^0123456789.]+"); while(sc.hasNextDouble()){ score=sc.nextDouble(); count++; sum=sum+score; System.out.println(score); } double aver=sum/count; System.out.println("平均成绩:"+aver); } catch(Exception exp){ System.out.println(exp); } } }
Java异常异常类程序运行时抛出的一些错误。Java使用throw抛出一个Exception子类的实例表示异常发生。大部分异常均直接或者间接继承Exception类。try-catch通过try-catch语句可以处理异常，一旦出现异常，将会执行catch中的语句。try { ... } catch(ExceptionSubClass1 e) { ... } catch(ExceptionSubClass2 e) { }注意：这些catch里的语句不可存在继承的关系。自定义异常类继承Exception以定义自己的异常类。一个方法定义时候可以使用throws声明要产生的异常，在需要抛出异常时候可以使用throw关键字抛出异常。ExceptionSubClass:public class BankException extends Exception { String message; public BankException(int m,int n) { message="入账资金"+m+"是负数或支出"+n+"是正数，不符合系统要求."; } public String warnMess() { return message; } } BankClass:public class Bank { private int money; public void income(int in,int out) throws BankException { if(in<=0||out>=0||in+out<=0) { throw new BankException(in,out); //方法抛出异常，导致方法结束 } int netIncome=in+out; System.out.printf("本次计算出的纯收入是:%d元\n",netIncome); money=money+netIncome; } public int getMoney() { return money; } }DemoClass:public class Demo { public static void main(String args[]) { Bank bank=new Bank(); try{ bank.income(200,-100); bank.income(300,-100); bank.income(400,-100); System.out.printf("银行目前有%d元\n",bank.getMoney()); bank.income(200, 100); bank.income(99999,-100); } catch(BankException e) { System.out.println("计算收益的过程出现如下问题:"); System.out.println(e.warnMess()); } System.out.printf("银行目前有%d元\n",bank.getMoney()); } }finally格式try { ... } catch(...) { ... } finally{ ... }无论catch有没有捕获异常，finally中语句都会被执行注意：如果catch中使用return，依然会执行finally中部分！如果使用system.exit(0)则不会执行。断言调试使用可以让代码不处理某项异常，直接终止程序程序正式运行的时候关闭断言，再需要debug时候再开启易于发现某些致命错误断言语法格式使用assert声明一条断言语句格式1assart booleanException;格式2assart booleanException:messageException;例如：assart number >= 0则条件不符合程序立即停止对于第二种类型，停止后还会输出“:”之后的内容启用方法默认关闭，编译时加入-ea参数开启断言Java常用类String类构造String对象常量对象String s = "Hello,String!";此字符串将加入常量池！String对象String t = new String("Hello,String!");t只是引用，存放真正字符串的内存地址如此，即使t和s内保存的字符串相同，但在不同位置，显然$s\neq t$对String对象输出，输出的是其实体。也可从一个String对象创建另一个String对象构造方法String(char[] a)使用字符数组创建一个String对象。String(char[] a, int sIndex, int eIndex)使用a数组的s~e区间建立一个String对象引用String常量两个String变量若同时引用同一个字面量，则其具有相同的引用，例如：string s1, s2; s1 = "abc"; s2 = "abc";则s1==s2，其原理是，在赋值时，检查常量池中是否已经存在此常量，如果已经存在不会重复创建此常量。字符串运算并置使用“+”连接两个字符串可以将两个字符串连接。常用方法public int length():返回字符串长度。public boolean equals(String s):比较是否与s相同（值相同）public boolean startWith(String s):判断字符串是否以s为前缀endWith():同上类推public int compareTo(String s):判断字符串与s的大小关系（字典序），若相同返回0，若字符串大于s则返回值大于0，反之小于0.public boolean contains(String s):判断是否包含spublic int indexOf(String s):判断s首次出现的位置，若没有返回-1lastIndexOf():s最后出现的位置，没有返回-1字符串与基本数据类型互相转化Integer类中的parseInt(String s)函数可以将s转化成整数。同理存在Long，Double等类下的相应函数转换。String类中提供了String valueOf(\<N\>)函数将基本数据类型转化为字符串。字符串转换为字符数组public void getChars(int start, int end, char[] c, int offset)public char[] toCharArray():返回字符串对应的char数组StringTokenizer类用于分割字符串（不使用正则表达式）构造方法StringTokenizer(String s):对s构造一个，使用默认的分割标记（空格，回车，Tab等）StringTokenizer(String s, String delim):对s构造一个分割器，使用delim中存在的字符分割。例：StringTokenizer fenxi = new ..("You#*are*##welcome", "*#");则所有连续为#或*的字符串都会被当做分隔符。方法hasMoreTokens()方法返回boolean表示之后还有没有单词String nextToken()返回下一个单词countTokens()返回之后还有几个单词Scanner类构造Scanner(String s)Scanner scanner = new Scanner(s);方法useDelimiter(正则)函数使用“正则”中字符分割字符串next()方法返回下一个字符串hasNext()表示是否存在下一个字符串nextInteger(), nextDouble等返回对应的下一个元素，若下一个元素不是对应类型则会抛出InputMismatchExceptionStringBuffer类StringBuffer对象StringBuffer可修改，而String不可修改。StringBuffer内存自动调整大小。构造方法StringBuffer():初始容量为16，超过后自动调整。StringBuffer(int size):初始容量为size，超过后自动调整。StringBuffer(String s):初始容量为s的长度加上16。使用(StringBuffer).length():得到保存其中的字符串长度(StringBuffer).capacity():得到其容器的容量常用方法StringBuffer append(String,Long,Double...):将括号中内容加入到字符串之后，返回该字符串引用。StringBuffer append(Object o):将o序列化（toString）后加入字符串尾部。public char charAt(int n):获得字符串下表为n的字符public void setCharAt(int n):修改下标为n的字符StringBuffer insert(int index, String str):将str插入到index指定的位置，返回当前对象的引用。public StringBuffer reverse():翻转当前字符串并返回（会更改当前字符串）。StringBuffer delete(int startIndex, int endIndex):删除下标为两数之间的字符，返回当前对象。String Buffer replace(int s, int e, String str):将s到e的字符用str替换。BigInteger类、Random类BigInteger类用于处理特别大的数BigInteger(String s):可以构造一个十进制数，若s中存在非数字会抛出NumberFormatException异常。方法public BigInteger add(BigInteger val):返回当前与val的和public BigInteger subtract(BigInteger val):差public BigInteger multiply(BigInteger val):积public BigInteger divide(BigInteger val):商public BigInteger remainder(BigInteger val):余public int compareTo(BigInteger val):返回1，-1,0表示当前对象大于，小于，等于valpublic BigInteger abs(): 返回当前对象绝对值public BigInteger pow(int a): 返回当前对象的a次幂public String toString():返回当前对象十进制表示public String toString(int p): 返回当前对象p进制表示Random类构造函数Random()Random(long seed)方法nextInt():返回一个int范围内整数nextInt(int n):返回一个0~n的整数（不包括n）。nextDouble():返回一个0~1.0的随机数（不包括1.0）nextBoolean():返回True或FalseClass类（反射）Class对象可以帮助程序创建其他类的实例。使用Class实例创建其他类实例public static Class forName(String className):返回一个和className指定的类相关的class对象。如果类在某个包中，则className必须带包名例如“java.util.Date”获得Class对象调用public Object newInstance():返回该Class对象对应类的一个实例，注意该实例必须具有无参构造方法。例import java.util.Date; class Rect { double width,height,area; public double getArea() { area = height*width; return area; } } public class Example8_20 { public static void main(String args[]) { try{ Class cs = Class.forName("Rect"); Rect rect = (Rect)cs.newInstance(); rect.width = 100; rect.height = 200; System.out.println("rect的面积"+rect.getArea()); cs = Class.forName("java.util.Date"); Date date = (Date)cs.newInstance(); System.out.println(String.format("%tF %<tT %<tA",date)); } catch(Exception e){ System.out.println(e.toString()); } } }