TensorRT量化第三课:动态范围的常用计算方法
Posted 爱听歌的周童鞋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TensorRT量化第三课:动态范围的常用计算方法相关的知识,希望对你有一定的参考价值。
目录
模型量化原理
前言
手写AI推出的全新TensorRT模型量化课程,链接。记录下个人学习笔记,仅供自己参考。
本次课程为第三课,主要讲解动态范围的常用计算方法。
课程大纲可看下面的思维导图
1.前情回顾
在之前的课程中我们学习了对称量化和非对称量化的知识,在tensorRT中的INT8量化使用的方法就是对称量化。上节课提出在对称量化中存在一个问题,就是当数据中存在极端值时,会对量化精度造成不利影响,这节课我们就一起来学习相关解决方案。
2.动态范围的常用计算方法
首先来看下本次课程的题目,动态范围的常用计算方法,之前似乎没有提过呀,有点抽象(🤔)。
动态范围(Dynamic Range)指的是输入数据中数值的范围,计算动态范围是为了确定量化时使用的比特位数(还是抽象😂)。个人理解计算动态范围就是为了获得更好的Scale,毕竟Scale会影响到整个量化的精度,而Scale的计算和输入数据的值域范围息息相关(即值域的动态范围),在上节课中非对称量化过程中Scale计算为 S c a l e = ( R m a x − R m i n ) ( Q m a x − Q m i n ) Scale = \\frac(Rmax-Rmin)(Qmax-Qmin) Scale=(Qmax−Qmin)(Rmax−Rmin),而对称量化过程中的Scale计算为 S c a l e = ∣ R m a x ∣ Q m a x Scale = \\frac|Rmax|Qmax Scale=Qmax∣Rmax∣,都与输入数据的数值范围相关。
现在来看看动态范围的计算方法,动态范围的计算方法与量化的方式相关,对称量化和非对称量化使用的计算方法略有不同。在对称量化中,通常采用的是
输入数据的绝对值的最大值作为动态范围的计算方法
;而在非对称量化中,通常采用最小值和最大值的差作为动态范围的计算方法
。经过上面分析就不难理解题目动态范围的常用计算方法,在之前对称量化中的动态范围的计算方法就是
Max
方法即采取输入数据的绝对值的最大值,但是这种计算方法存在问题,就是容易受到离散点即噪声的干扰,我们的考虑改进,或者说采取其它的动态范围计算方法,也就是本节课程的Histogram以及Entropy方法。
常用的动态范围计算方法包括:(from chatGPT)
- Max方法:在对称量化中直接取输入数据中的绝对值的最大值作为量化的最大值。这种方法简单易用,但容易受到噪声等异常数据的影响,导致动态范围不准确。
- Histogram方法:统计输入数据的直方图,根据先验知识获取某个范围内的数据,从而获得对称量化的最大值。这种方法可以减少噪声对动态范围的影响,但需要对直方图进行统计,计算复杂度较高。
- Entropy方法:将输入数据的概率密度函数近似为一个高斯分布,以最小化熵作为选择动态范围的准则。这种方法也可以在一定程度上减少噪声对动态范围的影响,但需要对概率密度函数进行拟合和计算熵,计算复杂度较高。
对称量化和非对称量化的选择与动态范围的计算方法有一定的关系。对称量化要求量化的最大值和最小值的绝对值相等,可以采用Max方法或Histogram方法进行计算。非对称量化则可以采用Entropy方法进行计算,以最小化量化后的误差。
3.Histogram
3.1 定义
直方图(histogram)是统计学中常用的一种图形,它将数据按照数值分组并统计每组数据的出现频率,然后将频率用柱状图的方式表示出来。直方图通常用于描述一组数据的分布情况,可以帮助人们了解数据的特征,例如数据的中心位置、离散程度、对称性、峰态等(from chatGPT)
下面是一张直方图的示例:
下面是该直方图生成的示例代码:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(1000)
plt.hist(data, bins=50)
plt.title("histgram")
plt.xlabel("value")
plt.ylabel("freq")
# plt.savefig("histgram.png", bbox_inches="tight")
plt.show()
在上述代码中首先使用numpy
生成了一组包含1000个随机数的数据,然后调用matplotlib
库中的plt.hist()
函数生成该数据的直方图。其中data
参数表示要绘制的数据,bins
表示直方图的柱子数量,这里设置为50个。
3.2 histogram实现
histogram方法
为什么能克服Max方法
中离散点即噪声干扰问题呢?主要在于直方图统计了数据出现的频率,它可以将数据按照一定的区间进行离散化处理,并计算每个区间中数据点的数量。这种方法相对于Max方法
来说,能够更好地反映数据的分布情况,从而更准确地评估数据的动态范围。我们假设数据服从正态分布,即离散点在两边,我们可以通过从两边向中间靠拢的方法,去除离散点,类似于双指针的方法,算法具体流程如下:
- 首先,统计输入数据的直方图和范围
- 然后定义左指针和右指针分别指向直方图的左边界和右边界
- 计算当前双指针之间的直方图覆盖率,如果小于等于设定的覆盖率阈值,则返回此刻的左指针指向的直方图值,如果不满足,则需要调整双指针的值,向中间靠拢
- 如果当前左指针所指向的直方图值大于右指针所指向的直方图值,则右指针左移,否则左指针右移
- 循环,直到双指针覆盖的区域满足要求
下面是该算法流程的一个简单示例:
其中0~5代表算法执行的步骤,首先双指针位于直方图的左右区间,然后计算其覆盖率发现不满足设定的阈值要求,计算此时的左指针的直方图值小于右指针的直方图值,左指针右移即步骤1,继续上述步骤发现覆盖率还是不满足,且此时右指针值小故右指针左移即步骤2,以此类推到步骤3、步骤4,到步骤4计算发现覆盖率满足阈值要求,故将此时的双指针的直方图值的绝对值返回即可。
通过上面的分析,示例代码如下:
def scale_cal(x):
max_val = np.max(np.abs(x))
return max_val / 127
def histogram_range(x):
hist, range = np.histogram(x, 100)
total = len(x)
left = 0
right = len(hist) - 1
limit = 0.99
while True:
cover_percent = hist[left:right].sum() / total
if cover_percent <= limit:
break
if hist[left] > hist[right]:
right -= 1
else:
left += 1
left_val = range[left]
right_val = range[right]
dynamic_range = max(abs(left_val), abs(right_val))
return dynamic_range / 127
if __name__ == "__main__":
np.random.seed(1)
data_float32 = np.random.randn(1000).astype('float32')
# print(f"input = data_float32")
scale = scale_cal(data_float32)
scale2 = histogram_range(data_float32)
print(f"scale = scale scale2 = scale2")
上述代码中histogram_range
函数是基于数据直方图计算缩放因子的,该函数先将数据进行直方图统计,然后使用双指针算法去除数据直方图中的离散点,最后通过剩余数据的动态范围计算出缩放因子。
3.3 思考
Histogram方法
虽然能够解决Max方法
中的离散点噪声问题,但是使用数据直方图进行动态范围的计算,要求数据能够比较均匀地覆盖到整个动态范围内。如果数据服从类似正态分布,则直方图的结果具有参考价值,因为此时的数据覆盖动态范围的概率较高。但如果数据分布极不均匀或出现大量离散群,则直方图计算的结果可能并不准确。此时可考虑其它的动态范围计算方法。
3.4 拓展
np.histogram
是用于生成直方图的函数,其参数和返回值如下:
参数:
a
:待处理的数据,可以是一维或者多维数组,多维数组将会被展开成一维数组bins
:表示数据分成的区间数range
:表示数据的取值范围,可以是一个元组或数组density
:是否将直方图归一化。如果为True,直方图将归一化为概率密度函数。默认Falseweights
:每个数据点的权重,可以是一维数组或与a
数组相同的形状的数组。默认为None,表示所有的数据点权重相同。
返回值:
hist
:一个长度为bins
的一维数组,表示每个区间中数据点的数量或者归一化后的概率密度值。bin_edges
:长度为bins + 1
的一维数组,表示每个区间的边界。
总结
本次课程学习了动态范围计算中的直方图计算,该方法可解决Max方法中离散点问题,但同时要求数据的分布要均匀,不能出现过多的离散群。期待下次的动态范围计算方法Entropy😃
Jsp第三课 Servlet常用对象(综合案例)
概述
本次文章基于第三章的ServletConfig,ServletContext,HttpServletRequest,HttpServletResponse对象完成一个图书订阅系统的购买图书和查看图书购买记录功能。
搭建项目主页面
创建一个动态网站项目,在src中新建包com.book.servlet.
在包中,新建HomeServlet作为主页。效果图如下:
为了让一访问项目根路径地址就默认进入HomeServlet,这里需要将 HomeServlet的虚拟地址写入web.xml文件中作为默认的首页地址。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>Day07Jsp</display-name>
<welcome-file-list>
<welcome-file>HomeServlet</welcome-file>
</welcome-file-list>
</web-app>
HomeServlet中要完成超链接的显示,这里需要在该Servlet中定义两个静态数据,使用java注解的方式进行定义,代码如下:
package com.book.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class HomeServlet
*/
@WebServlet(urlPatterns = "/HomeServlet",
initParams = {
@WebInitParam(name = "buy",value = "购买图书"),
@WebInitParam(name = "selectOrder",value = "查看订单")
})
public class HomeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public HomeServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
然后在类中的空白处,重写init方法,用于加载两个静态数据,因这两个静态数据需要在其他方法中被使用,因此定义成全局变量。代码如下:
再重写init方法,并通过两个静态数据,存储至全局变量。
@Override
public void init(ServletConfig config) throws ServletException {
buy=config.getInitParameter("buy");
selectOrder=config.getInitParameter("selectOrder");
}
因浏览器访问服务器默认使用Get请求,这里我们在diGet方法中编写两个超链接发送给浏览器上显示:
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//当浏览器访问该Servlet的时候,servlet将静态数据发给浏览器显示
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter pw=response.getWriter();
pw.append("<div align='center'>");
pw.append("<a href=''>"+buy+"</a> ");
pw.append("<a href=''>"+selectOrder+"</a> ");
pw.append("</div>");
}
这里运行以后,可看到以上的效果图所示的页面。
定义图书实体类
因为我们需要展示多本图书供用户进行购买,而每一本图书都需要包含,书名,作者,单价,出版社,购买时间等等数据,因此为采用定义实体类的方式用于分别存储每一本书的这些数据信息。
接着我们在src文件夹中定义包com.book.entity,用于存储创建的所有实体类,点右键新建class类,类名为Book,在Book中定义五个全局变量用于存储以上五个信息,并对其进行数据封装,代码如下:
package com.book.entity;
/**
* 图书的实体类
* @author teacher
* 2021/10/14
*/
public class Book {
//java类的封装
private String bookName;//书名
private String author;//作者
private String price;//单价
private String address;//出版社
private String buyTime;//购买时间
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getBuyTime() {
return buyTime;
}
public void setBuyTime(String buyTime) {
this.buyTime = buyTime;
}
//生成所有变量的get和set方法
//构造方法的作用:
//1.用于创建这个类的对象
//2.用于快速给变量进行赋值
//生成无参构造方法
public Book() {
// TODO Auto-generated constructor stub
}
//生成有参构造方法
public Book(String bookName, String author, String price, String address, String buyTime) {
super();
this.bookName = bookName;
this.author = author;
this.price = price;
this.address = address;
this.buyTime = buyTime;
}
@Override
public String toString() {
return "Book [bookName=" + bookName + ", author=" + author + ", price=" + price + ", address=" + address
+ ", buyTime=" + buyTime + "]";
}
//生成toString方法,为了在做功能的时候,
//查看检查这个对象中是否有数据
}
定义临时图书库工具类
这里我们使用List集合临时将多本图书信息存储起来,用于显示在页面上供用户进行购买,这里我们在src文件夹中新建包com.book.util,在包中新建类BookDBUtil,在类中定义方法,创建多本图书信息,并将多本图书存储至List集合,代码如下:
package com.book.util;
/**
* 图书信息的工具类
* @author teahcer
* 2021/10/14
*用于临时存储一些图书信息
*/
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.book.entity.Book;
public class BookDataUtil {
//定义方法,用于存储多本书的信息
public List<Book> getBooks(){
Book book1=new Book("Java编程思想", "马云", "120.0元", "人民邮电出版社", "");
Book book2=new Book("MySQL数据库教程", "任正非", "89.0元", "清华大学出版社", "");
Book book3=new Book("Java疯狂讲义", "马化腾", "108.0元", "电子工业出版社", "");
Book book4=new Book("Python爬虫技术", "李彦宏", "66.0元", "新华出版社", "");
Book book5=new Book("C语言从入门到放弃", "李云龙", "56.0元", "江西出版社", "");
//创建List用于存储以上五本书
List<Book> oBooks=new ArrayList<Book>();
oBooks.add(book1);
oBooks.add(book2);
oBooks.add(book3);
oBooks.add(book4);
oBooks.add(book5);
return oBooks;
}
//获得当前实时时间
public static String getNowTime() {
//获得的现在实时时间是毫秒计算的
Date date=new Date();
//简单日期格式类 2021年10月20 日 13:55:30
SimpleDateFormat sdf=
new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
//按照设定的格式进行格式化
String time=sdf.format(date);
return time;
}
}
创建图书信息列表页面供用户购买图书
即多本图书已准备好,这里需要创建Servlet完成图书信息列表页面,类名为ShowBookInfoServlet,这里我们需要先从工具类中获得所有图书信息,并将该图书已表格的形式发送给浏览器上显示出来,其代码如下:
package com.book.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.book.entity.Book;
import com.book.util.BookDataUtil;
/**
* Servlet implementation class ShowBookInfoServlet
*/
@WebServlet("/ShowBookInfoServlet")
public class ShowBookInfoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public ShowBookInfoServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获得所有图书信息
BookDataUtil util=new BookDataUtil();
List<Book> oBooks=util.getBooks();
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter pw=response.getWriter();
pw.append("<div align='center'>");
pw.append("<table border='1' style='width:800px;text-align:center;'>");
//写标题
pw.append("<tr>");
pw.append("<td>书名</td>");
pw.append("<td>作者</td>");
pw.append("<td>单价</td>");
pw.append("<td>出版社</td>");
pw.append("<td>操作</td>");
pw.append("</tr>");
for (int i=0;i<oBooks.size();i++) {
pw.append("<tr>");
pw.append("<td>"+oBooks.get(i).getBookName()+"</td>");
pw.append("<td>"+oBooks.get(i).getAuthor()+"</td>");
pw.append("<td>"+oBooks.get(i).getPrice()+"</td>");
pw.append("<td>"+oBooks.get(i).getAddress()+"</td>");
pw.append("<td><a href='BuyBookServlet?index="+i+"'><input type='button' "
+ "style='background-color:green;' "
+ "value='购买'></a></td>");
pw.append("</tr>");
}
pw.append("</table>");
pw.append("</div>");
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
并在HomeServlet的首页的购买图书的超链接上添加图书信息列表页面的虚拟地址。
其运行后,点击购买图书即课跳转至图书信息列表页面,效果如下:
完成存储用户购买的图书记录信息
当用户点击任意一本图书进行购买的时候,这里我们采取发送给图书的下标给服务器的方式,能尽快的获取用户要购买的图书信息,这里新建一个Servlet类,类名为BuyBookServlet,用于接收用户将要购买的图书的下标,并查找出该图书,并通过方法获得当前购买图书的下单的实时时间,存储至该购买的图书中,然后将该图书存储至List集合,因为用户可能会购买多本图书,最后将所哟购买的图书记录信息存储至ServletContext对象中。
package com.book.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.book.entity.Book;
import com.book.util.BookDataUtil;
/**
* Servlet implementation class BuyBookServlet
*/
@WebServlet("/BuyBookServlet")
public class BuyBookServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public BuyBookServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获得用于点击购买按钮发送过来的请求
//获得浏览器发送过来的图书的下标
String index=request.getParameter("index");
//将接收的字符串类型转换成int类型
int i=Integer.parseInt(index);
//根据用户发送过来的下标,去查找用户将要购买的书籍
List<Book> oBooks=new BookDataUtil().getBooks();
Book book=oBooks.get(i);
//当用户购买图书的时候,需要获得当前实时时间
book.setBuyTime(BookDataUtil.getNowTime());
//获得ServletContext对象
ServletContext sc=getServletContext();
//判断ServletContext是否存在List集合
Object object=sc.getAttribute("orders");
List<Book> oList=null;
if (object==null) {
//说明以前从来没有购买过书
oList=new ArrayList<Book>();
oList.add(book);
}else {
//以前有购买书,只需要拿到List集合继续添加购买记录即可
//不需要重新创建List集合
oList=(List<Book>) object;
oList.add(book);
}
//保存完购买记录,之后存储至ServletContext对象
sc.setAttribute("orders", oList);
//购买成功之后,跳转至提示页面
//使用请求转发跳转,因为请求转发可以携带数据跳转页面
request.setAttribute("book", book);
request.getRequestDispatcher("AlertServlet")
.forward(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
创建提示信息页面
在图书记录信息存储后,需要提示用户购买的图书下单完成,是否需要继续购买的提示,这里新建一个Servlet类,类名为:AlertServlet
package com.book.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.book.entity.Book;
/**
* Servlet implementation class AlertServlet
*/
@WebServlet("/AlertServlet")
public class AlertServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public AlertServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Book book=(Book) request.getAttribute("book");
String info="您的《"+book.getBookName()+"》购买成功,"
+ "如果继续购买请<a href='ShowBookInfoServlet'>点击这里</a>,"
+ "或者5秒之后自动跳回首页";
response.setHeader("refresh", "5;url=HomeServlet");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter pw=response.getWriter();
pw.print(info);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
效果如下:
完成查看图书记录信息列表页面
用户在购买完图书信息之后,需要查看当前购买的所有图书的记录信息,这里我们新建Servlet类,类名为:SelectOrderServlet,从ServletContext类中获得所有的图书记录信息,展示在表格上,并需要判断是否有购买图书记录信息,其代码如下:
package com.book.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.book.entity.Book;
/**
* Servlet implementation class SelectOrderServlet
*/
@WebServlet("/SelectOrderServlet")
public class SelectOrderServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public SelectOrderServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//因为用户购买的图书信息被存储在ServletContext对象中
//要展示用户的购买记录,需要先获得ServletContext对象
ServletContext sc=getServletContext();
//从该对象中获得存储图书记录的List集合
Object object=sc.getAttribute("orders");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter pw=response.getWriter();
List<Book> oBooks=null;
if (object!=null) {
oBooks=(List<Book>) object;
pw.append("<div align='center'>");
pw.append("<table border='1' style='width:800px;text-align:center;'>");
//写标题
pw.append("<tr>");
pw.append("<td>书名</td>");
pw.append("<td>作者</td>");
pw.append("<td>单价</td>");
pw.append("<td>出版社</td>");
pw.append("<td>购买时间</td>");
pw.append("</tr>");
for (int i=0;i<oBooks.size();i++) {
pw.append("<tr>");
pw.append("<td>"+oBooks.get(i).getBookName()+"</td>");
pw.append("<td>"+oBooks.get(i).getAuthor()+"</td>");
pw.append("<td>"+oBooks.get(i).getPrice()+"</td>");
pw.append("<td>"+oBooks.get(i).getAddress()+"</td>");
pw.append("<td>"+oBooks.get(i).getBuyTime()+"</td>");
pw.append("</tr>");
}
pw.append("</table>");
pw.append("</div>");
}else {
pw.print("当前还没有购买图书的记录,请先购买再查看!!!");
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
并把给Servlet的虚拟地址添加至主页面的第二个超链接上,
其运行的效果如下:
本次文章就到这里,初学者可根据代码完成以上功能,可自行扩展修改图书信息以及删除购买记录的功能等等。
以上是关于TensorRT量化第三课:动态范围的常用计算方法的主要内容,如果未能解决你的问题,请参考以下文章