高性能编程实战
实战课题:
模拟个人账单,从数据库查询数据,输出文件;
数据准备–按日插入(本次共插入了2000条)购买商品消费记录,查询后按日统计输出文件;如下图所示
单任务版本实现
第一个版本代码完成
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HighCapabilityTask {
public static void main(String[] args) {
HighCapabilityTask task = new HighCapabilityTask();
long starttime = System.currentTimeMillis();
// for (int i = 0; i < 10; i++){
// task.task();
// }
task.task();
long endtime = System.currentTimeMillis();
System.out.println("任务执行时间:" + (endtime - starttime) + "ms");
}
public void task(){
Connection con;
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://www.5open.top:3306/lwltestDB?useUnicode=true&characterEncoding=UTF-8;useUnicode=true&characterEncoding=UTF-8";
String user = "lwl";
String password = "sinodata123";
ResultSet rs = null;
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
try {
System.out.println("开始连接数据库" + System.currentTimeMillis());
Class.forName(driver);
con = DriverManager.getConnection(url, user, password);
if (!con.isClosed()){
System.out.println(user + "登录成功" + System.currentTimeMillis());
}
Statement statement = con.createStatement();
String totalsql = "select buy_date,sum(goods_price) total_goods_price from goodsbill group by buy_date;";
System.out.println("结束查询总计" + System.currentTimeMillis());
rs = statement.executeQuery(totalsql);
Map<String,Goods> totalMap = new HashMap<>();;
//封装一个按日期统计的购买总计Map
while (rs.next()) {
Goods goods = new Goods();
String key = rs.getString("buy_date");
String total_goods_price = String.valueOf(rs.getDouble("total_goods_price"));
goods.setBuy_date(key);
goods.setGoods_price(total_goods_price);
totalMap.put(key,goods);
// System.out.println(key+"============"+goods.toString());
}
System.out.println("结束处理总计数据" + System.currentTimeMillis());
//封装按日统计明细Map
Map<String,List<Goods>> detailMap = new HashMap<>();
String detailsql = "select goods_id,goods_name,buy_date,goods_price,goods_describe from goodsbill order by buy_date;";
rs = statement.executeQuery(detailsql);
System.out.println("结束明细查询" + System.currentTimeMillis());
List<Goods> list;
while (rs.next()){
String goods_id = rs.getString("goods_id");
String goods_name = rs.getString("goods_name");
String buy_date = formatter.format(rs.getDate("buy_date"));
String goods_price = String.valueOf(rs.getDouble("goods_price"));
String goods_describe = rs.getString("goods_describe");
Goods goods = new Goods();
goods.setGoods_id(goods_id);
goods.setGoods_name(goods_name);
goods.setBuy_date(buy_date);
goods.setGoods_price(goods_price);
goods.setGoods_describe(goods_describe);
if (detailMap.containsKey(buy_date)){
detailMap.get(buy_date).add(goods);
}else {
list = new ArrayList<>();
list.add(goods);
detailMap.put(buy_date,list);
}
}
System.out.println("结束明细数据处理" + System.currentTimeMillis());
//输出到文件
String baseFilePath = "C:\\00wangyi\\subjecttask\\";
File file = new File(baseFilePath+"task"+System.currentTimeMillis());
if (!file.exists()||!file.isDirectory()){
file.mkdirs();
}
int numb = 0;
BufferedWriter buf = null;
for(Map.Entry<String, List<Goods>> entry : detailMap.entrySet()){
String mapKey = entry.getKey();
List<Goods> listdetailgoods = entry.getValue();
File filetasktxt = new File(file.getPath()+"\\"+mapKey+"消费账单.txt");
try {
filetasktxt.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
FileWriter fileWriter = new FileWriter(filetasktxt);
buf = new BufferedWriter(fileWriter);
buf.write("单日消费总金额:"+totalMap.get(mapKey).getGoods_price()+"元!!!\r\n");
buf.write("购买单号\t商品名称\t商品价格\t商品描述\r\n");
for(Goods goodsdetail : listdetailgoods){
buf.write(goodsdetail.getGoods_id()+"\t"+goodsdetail.getGoods_name()+"\t"+goodsdetail.getGoods_price()+"\t"+goodsdetail.getGoods_describe()+"\r\n");
// System.out.println(goodsdetail.toString());
numb++;
}
buf.close();
}
System.out.println("结束文件输出" + System.currentTimeMillis());
// System.out.println(numb);
rs.close();
con.close();
}catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (SQLException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}finally {
}
}
}
测试运行
分析:
数据库连接时间最长,本次执行是单个任务,多线程时可采用数据库连接池优化
明细查询sql,花的时间很长,实际sql很简单,这部分可能是网络原因;
数据处理涉及代码能力,暂缓执行;
文件输出部分,可以采用ForkJoin框架优化下,此次会输出36个文件,多开几个任务可能有提升;
ForkJoin优化文件输出部分代码
优化部分代码
//输出文件for循环修改部分
ForkJoinPool pool = new ForkJoinPool();
Iotask iotask = new Iotask(valuesList,file,totalMap,0,valuesList.size());
pool.invoke(iotask);//同步调用
//添加内部类实现框架功能
private static class Iotask extends RecursiveTask<Integer> {
private final static int BASISTASK = 15;//拆分基数
private List<List<Goods>> listsrc; //处理数据源
private int begin;
private int end;
private File file;
private Map<String,Goods> totalMap;
public Iotask(List<List<Goods>> listsrc, File file, Map<String,Goods> totalMap, int begin, int end) {
this.listsrc = listsrc;
this.begin = begin;
this.end = end;
this.file = file;
this.totalMap = totalMap;
}
@Override
protected Integer compute() {
if (end - begin < BASISTASK) {
int numb = 0;
try {
BufferedWriter buf = null;
for (int listindex = 0; listindex < listsrc.size(); listindex++){
String mapKey = listsrc.get(listindex).get(0).getBuy_date();
List<Goods> listdetailgoods = listsrc.get(listindex);
File filetasktxt = new File(file.getPath()+"\\"+mapKey+"消费账单.txt");
try {
filetasktxt.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
FileWriter fileWriter = new FileWriter(filetasktxt);
buf = new BufferedWriter(fileWriter);
buf.write("单日消费总金额:"+totalMap.get(mapKey).getGoods_price()+"元!!!\r\n");
buf.write("购买单号\t商品名称\t商品价格\t商品描述\r\n");
for(Goods goodsdetail : listdetailgoods){
buf.write(goodsdetail.getGoods_id()+"\t"+goodsdetail.getGoods_name()+"\t"+goodsdetail.getGoods_price()+"\t"+goodsdetail.getGoods_describe()+"\r\n");
numb++;
}
buf.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return numb;
}else {
int mid = (begin + end)/2;
Iotask left = new Iotask (listsrc,file,totalMap,begin,mid);
Iotask right = new Iotask (listsrc,file,totalMap,mid+1,end);
invokeAll(left,right);
return left.join()+right.join();
}
}
}
优化后稍有提升,多次运行,处理时间在150–200ms,提升的效果很小,应该跟写文件大小的原因,单次任务的IO读写只有120K,如果是大文件,效果应该很明显;
多任务时采用数据库连接池及线程池实现
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
public class HighCapabilityTask1 {
public static void main(String[] args) throws SQLException, InterruptedException {
HighCapabilityTask1 task = new HighCapabilityTask1();
int taskNumb = 10;
CountDownLatch latch = new CountDownLatch(taskNumb);//多线程情况下方便计算整个任务运行时间
long starttime = System.currentTimeMillis();
MysqlPool mysqlPool = new MysqlPool();
ExecutorService threadPool = new ThreadPoolExecutor(4,6,3, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(10));
System.out.println("开始执行任务"+starttime);
for (int i = 0; i < taskNumb; i++){
ThreadTask threadTask = new ThreadTask("task_" + i, latch, mysqlPool);
threadPool.execute(threadTask);
}
latch.await();
long endtime = System.currentTimeMillis();
threadPool.shutdown();
System.out.println("任务执行时间:" + (endtime - starttime) + "ms");
}
static class ThreadTask implements Runnable{
private String taskName;
private CountDownLatch latch;
private MysqlPool mysqlPool;
public ThreadTask (String taskName, CountDownLatch latch, MysqlPool mysqlPool){
this.taskName = taskName;
this.mysqlPool = mysqlPool;
this.latch = latch;
}
@Override
public void run() {
Connection con = null;
ResultSet rs = null;
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
try {
con = mysqlPool.getConnection();
if (!con.isClosed()){
System.out.println("成功拿到连接");
}
Statement statement = con.createStatement();
String totalsql = "select buy_date,sum(goods_price) total_goods_price from goodsbill group by buy_date;";
rs = statement.executeQuery(totalsql);
Map<String,Goods> totalMap = new HashMap<>();;
//封装一个按日期汇总的购买Map
while (rs.next()) {
Goods goods = new Goods();
String key = rs.getString("buy_date");
String total_goods_price = String.valueOf(rs.getDouble("total_goods_price"));
goods.setBuy_date(key);
goods.setGoods_price(total_goods_price);
totalMap.put(key,goods);
// System.out.println(key+"============"+goods.toString());
}
//封装按日统计明细Map
Map<String,List<Goods>> detailMap = new HashMap<>();
String detailsql = "select goods_id,goods_name,buy_date,goods_price,goods_describe from goodsbill order by buy_date;";
rs = statement.executeQuery(detailsql);
List<Goods> list;
while (rs.next()){
String goods_id = rs.getString("goods_id");
String goods_name = rs.getString("goods_name");
String buy_date = formatter.format(rs.getDate("buy_date"));
String goods_price = String.valueOf(rs.getDouble("goods_price"));
String goods_describe = rs.getString("goods_describe");
Goods goods = new Goods();
goods.setGoods_id(goods_id);
goods.setGoods_name(goods_name);
goods.setBuy_date(buy_date);
goods.setGoods_price(goods_price);
goods.setGoods_describe(goods_describe);
if (detailMap.containsKey(buy_date)){
detailMap.get(buy_date).add(goods);
}else {
list = new ArrayList<>();
list.add(goods);
detailMap.put(buy_date,list);
}
}
//输出到文件
String baseFilePath = "C:\\00wangyi\\subjecttask\\";
File file = new File(baseFilePath+"task"+System.currentTimeMillis());
if (!file.exists()||!file.isDirectory()){
file.mkdirs();
}
int numb = 0;
BufferedWriter buf = null;
for(Map.Entry<String, List<Goods>> entry : detailMap.entrySet()){
String mapKey = entry.getKey();
List<Goods> listdetailgoods = entry.getValue();
File filetasktxt = new File(file.getPath()+"\\"+mapKey+"消费账单.txt");
try {
filetasktxt.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
FileWriter fileWriter = new FileWriter(filetasktxt);
buf = new BufferedWriter(fileWriter);
buf.write("单日消费总金额:"+totalMap.get(mapKey).getGoods_price()+"元!!!\r\n");
buf.write("购买单号\t商品名称\t商品价格\t商品描述\r\n");
for(Goods goodsdetail : listdetailgoods){
buf.write(goodsdetail.getGoods_id()+"\t"+goodsdetail.getGoods_name()+"\t"+goodsdetail.getGoods_price()+"\t"+goodsdetail.getGoods_describe()+"\r\n");
// System.out.println(goodsdetail.toString());
numb++;
}
buf.close();
}
// System.out.println(numb);
rs.close();
}catch (SQLException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}finally {
latch.countDown();
mysqlPool.releaseConnection(con);
}
System.out.println(taskName+"执行完成"+System.currentTimeMillis());
}
}
}
采用连接池和线程池后,当执行任务数量为10个时执行速度差别在3s左右,任务个数越大差距越明显,这意味着,同样配置的服务器,可以处理的请求更多了。
相关代码实现
实体类
public class Goods {
private String goods_id;
private String goods_name;
private String buy_date;
private String goods_price;
private String goods_describe;
public String getGoods_id() {
return goods_id;
}
public void setGoods_id(String goods_id) {
this.goods_id = goods_id;
}
public String getGoods_name() {
return goods_name;
}
public void setGoods_name(String goods_name) {
this.goods_name = goods_name;
}
public String getBuy_date() {
return buy_date;
}
public void setBuy_date(String buy_date) {
this.buy_date = buy_date;
}
public String getGoods_price() {
return goods_price;
}
public void setGoods_price(String goods_price) {
this.goods_price = goods_price;
}
public String getGoods_describe() {
return goods_describe;
}
public void setGoods_describe(String goods_describe) {
this.goods_describe = goods_describe;
}
@Override
public String toString() {
return goods_id +","+ goods_name+"," +buy_date +","+ goods_price+"," + goods_describe;
}
}
数据插入脚本
public class InsertGoodsSql {
public static void main(String[] args) {
InsertGoodsSql da = new InsertGoodsSql();
da.insertGoods();
}
public List<String> insertGoods(){
List<String> list = new ArrayList<>();
LocalDate initData = LocalDate.of(2019,12,1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
DecimalFormat df = new DecimalFormat("#.00");
Random r = new Random();
int max = 1000;
String sql = "";
for (int i = 0; i < max;){
int m = r.nextInt(99)+1;
i = i + m;
if (i >= max){
m = m - i + max;
}
System.out.println(""+m);
for (int j = 1; j <= m; j++){
double rs = r.nextDouble() * r.nextInt(1000);
String strrs = df.format(rs);
String nowText = initData.format(formatter);
String daynumb = String.valueOf(j);
if (daynumb.length()==1){
daynumb = "0" + daynumb;
}
String goods_id = nowText+daynumb;
String goods_name = "商品"+goods_id;
String buy_date = nowText;
String goods_price = strrs;
String goods_describe = "再忍不住,剁手!";
sql = "INSERT INTO goodsbill ( goods_id, goods_name,buy_date,goods_price,goods_describe ) VALUES " +
" ( '"+goods_id+"', '"+goods_name+"','"+buy_date+"',"+goods_price+" ,'"+goods_describe+"');";
// System.out.print(nowText+daynumb+"\t"+strrs+"\t");
System.out.println(sql);
list.add(sql);
}
System.out.println("");
initData = initData.plusDays(1);
}
return list;
}
}
public class GoodsSql {
public static void main(String[] args) {
Connection con;
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://www.5open.top:3306/lwltestDB?useUnicode=true&characterEncoding=UTF-8;useUnicode=true&characterEncoding=UTF-8";
String user = "lwl";
String password = "sinodata123";
int n = 0;
try {
Class.forName(driver);
con = DriverManager.getConnection(url, user, password);
if (!con.isClosed()){
System.out.println(user + "登录成功");
}
Statement statement = con.createStatement();
int rs ;
List<String> listsql = new InsertGoodsSql().insertGoods();
for(String strsql : listsql){
rs = statement.executeUpdate(strsql);
n = n + rs;
}
System.out.println(n);
con.close();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
catch (SQLException e) {
e.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
finally {
System.out.println("已向数据库插入" + n + "条数据!");
}
}
}
数据库连接池简单实现
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
public class MysqlPool {
private static String driver = "com.mysql.jdbc.Driver";
private static String url = "jdbc:mysql://www.5open.top:3306/lwltestDB?useUnicode=true&characterEncoding=UTF-8;useUnicode=true&characterEncoding=UTF-8";
private static String user = "lwl";
private static String password = "sinodata123";
private static int initConnectSize = 3;//初始连接数
private static int maxConnections = 5;//最大连接数
private static LinkedList<Connection> pool = new LinkedList<>();//存放连接对象
public MysqlPool (){
try {
Class.forName(driver);
for (int i = 0; i < initConnectSize; i++){
Connection con = DriverManager.getConnection(url, user, password);
pool.addLast(con);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getConnection () throws SQLException{
Connection newConnection = null;
synchronized (pool){
if (pool.isEmpty()){
newConnection = DriverManager.getConnection(url, user, password);
}else {
newConnection = pool.removeFirst();
}
}
return newConnection;
}
public void releaseConnection (Connection conn){
synchronized (pool){
pool.addLast(conn);
}
}
建表sql
CREATE TABLE IF NOT EXISTS `goodsDill`(
`goods_id` VARCHAR(10) COMMENT '商品订单号',
`goods_name` VARCHAR(40) COMMENT '商品名称',
`buy_date` DATE COMMENT '商品订单号',
`goods_price` DOUBLE(10,2) COMMENT '商品价格',
`goods_describe` VARCHAR(100) COMMENT '商品描述',
PRIMARY KEY ( `goods_id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
高性能编程虽然是基础但特别重要,实例中的数据库连接池简陋,IO可采用通道及缓存复用思想可能优化更多,学习中