热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

easypoiexcelutil导入都是null_导入:Java实现大批量数据导入导出(100W以上)

阅读文本大概需要3分钟。来源:https:www.cnblogs.combarrywxxp10700221.html最近业务方有一个需求,需要一次导入超

阅读文本大概需要3分钟。

来源:https://www.cnblogs.com/barrywxx/p/10700221.html

最近业务方有一个需求,需要一次导入超过100万数据到系统数据库。可能大家首先会想,这么大的数据,干嘛通过程序去实现导入,为什么不直接通过SQL导入到数据库。

一、为什么一定要在代码实现

说说为什么不能通过SQL直接导入到数据库,而是通过程序实现:

1. 首先&#xff0c;这个导入功能开始提供页面导入&#xff0c;只是开始业务方保证的一次只有<3W的数据导入&#xff1b;

2. 其次&#xff0c;业务方导入的内容需要做校验&#xff0c;比如门店号&#xff0c;商品号等是否系统存在&#xff0c;需要程序校验&#xff1b;

3. 最后&#xff0c;业务方导入的都是编码&#xff0c;数据库中还要存入对应名称&#xff0c;方便后期查询&#xff0c;SQL导入也是无法实现的。

基于以上上三点&#xff0c;就无法直接通过SQL语句导入数据库。那就只能老老实实的想办法通过程序实现。

二、程序实现有以下技术难点

1. 一次读取这么大的数据量&#xff0c;肯定会导致服务器内存溢出&#xff1b;

2. 调用接口保存一次传输数据量太大&#xff0c;网络传输压力会很大&#xff1b;

3. 最终通过SQL一次批量插入&#xff0c;对数据库压力也比较大&#xff0c;如果业务同时操作这个表数据&#xff0c;很容易造成死锁。

三、解决思路

根据列举的技术难点我的解决思路是&#xff1a;

1. 既然一次读取整个导入文件&#xff0c;那就先将文件流上传到服务器磁盘&#xff0c;然后分批从磁盘读取(支持多线程读取)&#xff0c;这样就防止内存溢出&#xff1b;

2. 调用插入数据库接口也是根据分批读取的内容进行调用&#xff1b;

3. 分批插入数据到数据库。

四、具体实现代码

1. 流式上传文件到服务器磁盘

 略&#xff0c;一般Java上传就可以实现&#xff0c;这里就不贴出。

2. 多线程分批从磁盘读取

批量读取文件&#xff1a;

  1. import org.slf4j.Logger;

  2. import org.slf4j.LoggerFactory;

  3. import java.io.File;

  4. import java.io.FileNotFoundException;

  5. import java.io.RandomAccessFile;

  6. import java.nio.ByteBuffer;

  7. import java.nio.channels.FileChannel;

  8. /**

  9. * 类功能描述&#xff1a;批量读取文件

  10. *

  11. * &#64;author WangXueXing create at 19-3-14 下午6:47

  12. * &#64;version 1.0.0

  13. */

  14. public class BatchReadFile {

  15. private final Logger LOGGER &#61; LoggerFactory.getLogger(BatchReadFile.class);

  16. /**

  17. * 字符集UTF-8

  18. */

  19. public static final String CHARSET_UTF8 &#61; "UTF-8";

  20. /**

  21. * 字符集GBK

  22. */

  23. public static final String CHARSET_GBK &#61; "GBK";

  24. /**

  25. * 字符集gb2312

  26. */

  27. public static final String CHARSET_GB2312 &#61; "gb2312";

  28. /**

  29. * 文件内容分割符&#xff0d;逗号

  30. */

  31. public static final String SEPARATOR_COMMA &#61; ",";

  32. private int bufSize &#61; 1024;

  33. // 换行符

  34. private byte key &#61; "\n".getBytes()[0];

  35. // 当前行数

  36. private long lineNum &#61; 0;

  37. // 文件编码,默认为gb2312

  38. private String encode &#61; CHARSET_GB2312;

  39. // 具体业务逻辑监听器

  40. private ReaderFileListener readerListener;

  41. public void setEncode(String encode) {

  42. this.encode &#61; encode;

  43. }

  44. public void setReaderListener(ReaderFileListener readerListener) {

  45. this.readerListener &#61; readerListener;

  46. }

  47. /**

  48. * 获取准确开始位置

  49. * &#64;param file

  50. * &#64;param position

  51. * &#64;return

  52. * &#64;throws Exception

  53. */

  54. public long getStartNum(File file, long position) throws Exception {

  55. long startNum &#61; position;

  56. FileChannel fcin &#61; new RandomAccessFile(file, "r").getChannel();

  57. fcin.position(position);

  58. try {

  59. int cache &#61; 1024;

  60. ByteBuffer rBuffer &#61; ByteBuffer.allocate(cache);

  61. // 每次读取的内容

  62. byte[] bs &#61; new byte[cache];

  63. // 缓存

  64. byte[] tempBs &#61; new byte[0];

  65. while (fcin.read(rBuffer) !&#61; -1) {

  66. int rSize &#61; rBuffer.position();

  67. rBuffer.rewind();

  68. rBuffer.get(bs);

  69. rBuffer.clear();

  70. byte[] newStrByte &#61; bs;

  71. // 如果发现有上次未读完的缓存,则将它加到当前读取的内容前面

  72. if (null !&#61; tempBs) {

  73. int tL &#61; tempBs.length;

  74. newStrByte &#61; new byte[rSize &#43; tL];

  75. System.arraycopy(tempBs, 0, newStrByte, 0, tL);

  76. System.arraycopy(bs, 0, newStrByte, tL, rSize);

  77. }

  78. // 获取开始位置之后的第一个换行符

  79. int endIndex &#61; indexOf(newStrByte, 0);

  80. if (endIndex !&#61; -1) {

  81. return startNum &#43; endIndex;

  82. }

  83. tempBs &#61; substring(newStrByte, 0, newStrByte.length);

  84. startNum &#43;&#61; 1024;

  85. }

  86. } finally {

  87. fcin.close();

  88. }

  89. return position;

  90. }

  91. /**

  92. * 从设置的开始位置读取文件&#xff0c;一直到结束为止。如果 end设置为负数,刚读取到文件末尾

  93. * &#64;param fullPath

  94. * &#64;param start

  95. * &#64;param end

  96. * &#64;throws Exception

  97. */

  98. public void readFileByLine(String fullPath, long start, long end) throws Exception {

  99. File fin &#61; new File(fullPath);

  100. if (!fin.exists()) {

  101. throw new FileNotFoundException("没有找到文件&#xff1a;" &#43; fullPath);

  102. }

  103. FileChannel fileChannel &#61; new RandomAccessFile(fin, "r").getChannel();

  104. fileChannel.position(start);

  105. try {

  106. ByteBuffer rBuffer &#61; ByteBuffer.allocate(bufSize);

  107. // 每次读取的内容

  108. byte[] bs &#61; new byte[bufSize];

  109. // 缓存

  110. byte[] tempBs &#61; new byte[0];

  111. String line;

  112. // 当前读取文件位置

  113. long nowCur &#61; start;

  114. while (fileChannel.read(rBuffer) !&#61; -1) {

  115. int rSize &#61; rBuffer.position();

  116. rBuffer.rewind();

  117. rBuffer.get(bs);

  118. rBuffer.clear();

  119. byte[] newStrByte;

  120. //去掉表头

  121. if(nowCur &#61;&#61; start){

  122. int firstLineIndex &#61; indexOf(bs, 0);

  123. int newByteLenth &#61; bs.length-firstLineIndex-1;

  124. newStrByte &#61; new byte[newByteLenth];

  125. System.arraycopy(bs, firstLineIndex&#43;1, newStrByte, 0, newByteLenth);

  126. } else {

  127. newStrByte &#61; bs;

  128. }

  129. // 如果发现有上次未读完的缓存,则将它加到当前读取的内容前面

  130. if (null !&#61; tempBs && tempBs.length !&#61; 0) {

  131. int tL &#61; tempBs.length;

  132. newStrByte &#61; new byte[rSize &#43; tL];

  133. System.arraycopy(tempBs, 0, newStrByte, 0, tL);

  134. System.arraycopy(bs, 0, newStrByte, tL, rSize);

  135. }

  136. // 是否已经读到最后一位

  137. boolean isEnd &#61; false;

  138. nowCur &#43;&#61; bufSize;

  139. // 如果当前读取的位数已经比设置的结束位置大的时候&#xff0c;将读取的内容截取到设置的结束位置

  140. if (end > 0 && nowCur > end) {

  141. // 缓存长度 - 当前已经读取位数 - 最后位数

  142. int l &#61; newStrByte.length - (int) (nowCur - end);

  143. newStrByte &#61; substring(newStrByte, 0, l);

  144. isEnd &#61; true;

  145. }

  146. int fromIndex &#61; 0;

  147. int endIndex &#61; 0;

  148. // 每次读一行内容&#xff0c;以 key(默认为\n) 作为结束符

  149. while ((endIndex &#61; indexOf(newStrByte, fromIndex)) !&#61; -1) {

  150. byte[] bLine &#61; substring(newStrByte, fromIndex, endIndex);

  151. line &#61; new String(bLine, 0, bLine.length, encode);

  152. lineNum&#43;&#43;;

  153. // 输出一行内容&#xff0c;处理方式由调用方提供

  154. readerListener.outLine(line.trim(), lineNum, false);

  155. fromIndex &#61; endIndex &#43; 1;

  156. }

  157. // 将未读取完成的内容放到缓存中

  158. tempBs &#61; substring(newStrByte, fromIndex, newStrByte.length);

  159. if (isEnd) {

  160. break;

  161. }

  162. }

  163. // 将剩下的最后内容作为一行&#xff0c;输出&#xff0c;并指明这是最后一行

  164. String lineStr &#61; new String(tempBs, 0, tempBs.length, encode);

  165. readerListener.outLine(lineStr.trim(), lineNum, true);

  166. } finally {

  167. fileChannel.close();

  168. fin.deleteOnExit();

  169. }

  170. }

  171. /**

  172. * 查找一个byte[]从指定位置之后的一个换行符位置

  173. *

  174. * &#64;param src

  175. * &#64;param fromIndex

  176. * &#64;return

  177. * &#64;throws Exception

  178. */

  179. private int indexOf(byte[] src, int fromIndex) throws Exception {

  180. for (int i &#61; fromIndex; i

  181. if (src[i] &#61;&#61; key) {

  182. return i;

  183. }

  184. }

  185. return -1;

  186. }

  187. /**

  188. * 从指定开始位置读取一个byte[]直到指定结束位置为止生成一个全新的byte[]

  189. *

  190. * &#64;param src

  191. * &#64;param fromIndex

  192. * &#64;param endIndex

  193. * &#64;return

  194. * &#64;throws Exception

  195. */

  196. private byte[] substring(byte[] src, int fromIndex, int endIndex) throws Exception {

  197. int size &#61; endIndex - fromIndex;

  198. byte[] ret &#61; new byte[size];

  199. System.arraycopy(src, fromIndex, ret, 0, size);

  200. return ret;

  201. }

  202. }

以上是关键代码&#xff1a;利用FileChannel与ByteBuffer从磁盘中分批读取数据

多线程调用批量读取&#xff1a; 

  1. /**

  2. * 类功能描述: 线程读取文件

  3. *

  4. * &#64;author WangXueXing create at 19-3-14 下午6:51

  5. * &#64;version 1.0.0

  6. */

  7. public class ReadFileThread extends Thread {

  8. private ReaderFileListener processDataListeners;

  9. private String filePath;

  10. private long start;

  11. private long end;

  12. private Thread preThread;

  13. public ReadFileThread(ReaderFileListener processDataListeners,

  14. long start,long end,

  15. String file) {

  16. this(processDataListeners, start, end, file, null);

  17. }

  18. public ReadFileThread(ReaderFileListener processDataListeners,

  19. long start,long end,

  20. String file,

  21. Thread preThread) {

  22. this.setName(this.getName()&#43;"-ReadFileThread");

  23. this.start &#61; start;

  24. this.end &#61; end;

  25. this.filePath &#61; file;

  26. this.processDataListeners &#61; processDataListeners;

  27. this.preThread &#61; preThread;

  28. }

  29. &#64;Override

  30. public void run() {

  31. BatchReadFile readFile &#61; new BatchReadFile();

  32. readFile.setReaderListener(processDataListeners);

  33. readFile.setEncode(processDataListeners.getEncode());

  34. try {

  35. readFile.readFileByLine(filePath, start, end &#43; 1);

  36. if(this.preThread !&#61; null){

  37. this.preThread.join();

  38. }

  39. } catch (Exception e) {

  40. throw new RuntimeException(e);

  41. }

  42. }

  43. }

监听读取&#xff1a;

  1. import java.util.ArrayList;

  2. import java.util.List;

  3. /**

  4. * 类功能描述&#xff1a;读文件监听父类

  5. *

  6. * &#64;author WangXueXing create at 19-3-14 下午6:52

  7. * &#64;version 1.0.0

  8. */

  9. public abstract class ReaderFileListener {

  10. // 一次读取行数&#xff0c;默认为1000

  11. private int readColNum &#61; 1000;

  12. /**

  13. * 文件编码

  14. */

  15. private String encode;

  16. /**

  17. * 分批读取行列表

  18. */

  19. private List rowList &#61; new ArrayList<>();

  20. /**

  21. *其他参数

  22. */

  23. private T otherParams;

  24. /**

  25. * 每读取到一行数据&#xff0c;添加到缓存中

  26. * &#64;param lineStr 读取到的数据

  27. * &#64;param lineNum 行号

  28. * &#64;param over 是否读取完成

  29. * &#64;throws Exception

  30. */

  31. public void outLine(String lineStr, long lineNum, boolean over) throws Exception {

  32. if(null !&#61; lineStr && !lineStr.trim().equals("")){

  33. rowList.add(lineStr);

  34. }

  35. if (!over && (lineNum % readColNum &#61;&#61; 0)) {

  36. output(rowList);

  37. rowList &#61; new ArrayList<>();

  38. } else if (over) {

  39. output(rowList);

  40. rowList &#61; new ArrayList<>();

  41. }

  42. }

  43. /**

  44. * 批量输出

  45. *

  46. * &#64;param stringList

  47. * &#64;throws Exception

  48. */

  49. public abstract void output(List stringList) throws Exception;

  50. /**

  51. * 设置一次读取行数

  52. * &#64;param readColNum

  53. */

  54. protected void setReadColNum(int readColNum) {

  55. this.readColNum &#61; readColNum;

  56. }

  57. public String getEncode() {

  58. return encode;

  59. }

  60. public void setEncode(String encode) {

  61. this.encode &#61; encode;

  62. }

  63. public T getOtherParams() {

  64. return otherParams;

  65. }

  66. public void setOtherParams(T otherParams) {

  67. this.otherParams &#61; otherParams;

  68. }

  69. public List getRowList() {

  70. return rowList;

  71. }

  72. public void setRowList(List rowList) {

  73. this.rowList &#61; rowList;

  74. }

  75. }

实现监听读取并分批调用插入数据接口&#xff1a;

  1. import com.today.api.finance.ImportServiceClient;

  2. import com.today.api.finance.request.ImportRequest;

  3. import com.today.api.finance.response.ImportResponse;

  4. import com.today.api.finance.service.ImportService;

  5. import com.today.common.Constants;

  6. import com.today.domain.StaffSimpInfo;

  7. import com.today.util.EmailUtil;

  8. import com.today.util.UserSessionHelper;

  9. import com.today.util.readfile.ReadFile;

  10. import com.today.util.readfile.ReadFileThread;

  11. import com.today.util.readfile.ReaderFileListener;

  12. import org.slf4j.Logger;

  13. import org.slf4j.LoggerFactory;

  14. import org.springframework.beans.factory.annotation.Value;

  15. import org.springframework.stereotype.Service;

  16. import org.springframework.util.StringUtils;

  17. import java.io.File;

  18. import java.io.FileInputStream;

  19. import java.util.ArrayList;

  20. import java.util.Arrays;

  21. import java.util.List;

  22. import java.util.concurrent.FutureTask;

  23. import java.util.stream.Collectors;

  24. /**

  25. * 类功能描述&#xff1a;报表导入服务实现

  26. *

  27. * &#64;author WangXueXing create at 19-3-19 下午1:43

  28. * &#64;version 1.0.0

  29. */

  30. &#64;Service

  31. public class ImportReportServiceImpl extends ReaderFileListener {

  32. private final Logger LOGGER &#61; LoggerFactory.getLogger(ImportReportServiceImpl.class);

  33. &#64;Value("${READ_COL_NUM_ONCE}")

  34. private String readColNum;

  35. &#64;Value("${REPORT_IMPORT_RECEIVER}")

  36. private String reportImportReceiver;

  37. /**

  38. * 财务报表导入接口

  39. */

  40. private ImportService service &#61; new ImportServiceClient();

  41. /**

  42. * 读取文件内容

  43. * &#64;param file

  44. */

  45. public void readTxt(File file, ImportRequest importRequest) throws Exception {

  46. this.setOtherParams(importRequest);

  47. ReadFile readFile &#61; new ReadFile();

  48. try(FileInputStream fis &#61; new FileInputStream(file)){

  49. int available &#61; fis.available();

  50. long maxThreadNum &#61; &#xff13;L;

  51. // 线程粗略开始位置

  52. long i &#61; available / maxThreadNum;

  53. this.setRowList(new ArrayList<>());

  54. StaffSimpInfo staffSimpInfo &#61; ((StaffSimpInfo)UserSessionHelper.getCurrentUserInfo().getData());

  55. String finalReportReceiver &#61; getEmail(staffSimpInfo.getEmail(), reportImportReceiver);

  56. this.setReadColNum(Integer.parseInt(readColNum));

  57. this.setEncode(ReadFile.CHARSET_GB2312);

  58. //这里单独使用一个线程是为了当maxThreadNum大于1的时候&#xff0c;统一管理这些线程

  59. new Thread(()->{

  60. Thread preThread &#61; null;

  61. FutureTask futureTask &#61; null ;

  62. try {

  63. for (long j &#61; 0; j

  64. //计算精确开始位置

  65. long startNum &#61; j &#61;&#61; 0 ? 0 : readFile.getStartNum(file, i * j);

  66. long endNum &#61; j &#43; 1

  67. //具体监听实现

  68. preThread &#61; new ReadFileThread(this, startNum, endNum, file.getPath(), preThread);

  69. futureTask &#61; new FutureTask(preThread, new Object());

  70. futureTask.run();

  71. }

  72. if(futureTask.get() !&#61; null) {

  73. EmailUtil.sendEmail(EmailUtil.REPORT_IMPORT_EMAIL_PREFIX, finalReportReceiver, "导入报表成功", "导入报表成功" ); //todo 等文案

  74. }

  75. } catch (Exception e){

  76. futureTask.cancel(true);

  77. try {

  78. EmailUtil.sendEmail(EmailUtil.REPORT_IMPORT_EMAIL_PREFIX, finalReportReceiver, "导入报表失败", e.getMessage());

  79. } catch (Exception e1){

  80. //ignore

  81. LOGGER.error("发送邮件失败", e1);

  82. }

  83. LOGGER.error("导入报表类型:"&#43;importRequest.getReportType()&#43;"失败", e);

  84. } finally {

  85. futureTask.cancel(true);

  86. }

  87. }).start();

  88. }

  89. }

  90. private String getEmail(String infoEmail, String reportImportReceiver){

  91. if(StringUtils.isEmpty(infoEmail)){

  92. return reportImportReceiver;

  93. }

  94. return infoEmail;

  95. }

  96. /**

  97. * 每批次调用导入接口

  98. * &#64;param stringList

  99. * &#64;throws Exception

  100. */

  101. &#64;Override

  102. public void output(List stringList) throws Exception {

  103. ImportRequest importRequest &#61; this.getOtherParams();

  104. List> dataList &#61; stringList.stream()

  105. .map(x->Arrays.asList(x.split(ReadFile.SEPARATOR_COMMA)).stream().map(String::trim).collect(Collectors.toList()))

  106. .collect(Collectors.toList());

  107. LOGGER.info("上传数据:{}", dataList);

  108. importRequest.setDataList(dataList);

  109. // LOGGER.info("request对象&#xff1a;{}",importRequest, "request增加请求字段&#xff1a;{}", importRequest.data);

  110. ImportResponse importResponse &#61; service.batchImport(importRequest);

  111. LOGGER.info("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;SUCESS_CODE&#61;&#61;&#61;&#61;&#61;&#61;&#61;"&#43;importResponse.getCode());

  112. //导入错误,输出错误信息

  113. if(!Constants.SUCESS_CODE.equals(importResponse.getCode())){

  114. LOGGER.error("导入报表类型:"&#43;importRequest.getReportType()&#43;"失败","返回码为&#xff1a;", importResponse.getCode() ,"返回信息&#xff1a;",importResponse.getMessage());

  115. throw new RuntimeException("导入报表类型:"&#43;importRequest.getReportType()&#43;"失败"&#43;"返回码为&#xff1a;"&#43; importResponse.getCode() &#43;"返回信息&#xff1a;"&#43;importResponse.getMessage());

  116. }

  117. // if(importResponse.data !&#61; null && importResponse.data.get().get("batchImportFlag")!&#61;null) {

  118. // LOGGER.info("eywa-service请求batchImportFlag不为空");

  119. // }

  120. importRequest.setData(importResponse.data);

  121. }

  122. }

注意&#xff1a;第53行代码&#xff1a;long maxThreadNum &#61; &#xff13;L;
就是设置分批读取磁盘文件的线程数&#xff0c;我设置为&#xff13;&#xff0c;大家不要设置太大&#xff0c;不然多个线程读取到内存&#xff0c;也会造成服务器内存溢出。

以上所有批次的批量读取并调用插入接口都成功发送邮件通知给导入人&#xff0c;任何一个批次失败直接发送失败邮件。 

数据库分批插入数据&#xff1a;

  1. /**

  2. * 批量插入非联机第三方导入账单

  3. * &#64;param dataList

  4. */

  5. def insertNonOnlinePayment(dataList: List[NonOnlineSourceData]) : Unit &#61; {

  6. if (dataList.nonEmpty) {

  7. CheckAccountDataSource.mysqlData.withConnection { conn &#61;>

  8. val sql &#61;

  9. s""" INSERT INTO t_pay_source_data

  10. (store_code,

  11. store_name,

  12. source_date,

  13. order_type,

  14. trade_type,

  15. third_party_payment_no,

  16. business_type,

  17. business_amount,

  18. trade_time,

  19. created_at,

  20. updated_at)

  21. VALUES (?,?,?,?,?,?,?,?,?,NOW(),NOW())"""

  22. conn.setAutoCommit(false)

  23. var stmt &#61; conn.prepareStatement(sql)

  24. var i &#61; 0

  25. dataList.foreach { x &#61;>

  26. stmt.setString(1, x.storeCode)

  27. stmt.setString(2, x.storeName)

  28. stmt.setString(3, x.sourceDate)

  29. stmt.setInt(4, x.orderType)

  30. stmt.setInt(5, x.tradeType)

  31. stmt.setString(6, x.tradeNo)

  32. stmt.setInt(7, x.businessType)

  33. stmt.setBigDecimal(8, x.businessAmount.underlying())

  34. stmt.setString(9, x.tradeTime.getOrElse(null))

  35. stmt.addBatch()

  36. if ((i % 5000 &#61;&#61; 0) && (i !&#61; 0)) { //分批提交

  37. stmt.executeBatch

  38. conn.commit

  39. conn.setAutoCommit(false)

  40. stmt &#61; conn.prepareStatement(sql)

  41. }

  42. i &#43;&#61; 1

  43. }

  44. stmt.executeBatch()

  45. conn.commit()

  46. }

  47. }

  48. }

 以上代码实现每5000 行提交一次批量插入&#xff0c;防止一次提较数据库的压力。

往期精彩

01 漫谈发版哪些事&#xff0c;好课程推荐

02 Linux的常用最危险的命令

03 精讲Spring Boot—入门&#43;进阶&#43;实例

04 优秀的Java程序员必须了解的GC哪些

05 互联网支付系统整体架构详解

关注我

每天进步一点点

d913193339d39c2465012ef40d92364d.png

喜欢&#xff01;在看☟



推荐阅读
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 本文介绍了使用cacti监控mssql 2005运行资源情况的操作步骤,包括安装必要的工具和驱动,测试mssql的连接,配置监控脚本等。通过php连接mssql来获取SQL 2005性能计算器的值,实现对mssql的监控。详细的操作步骤和代码请参考附件。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • 延迟注入工具(python)的SQL脚本
    本文介绍了一个延迟注入工具(python)的SQL脚本,包括使用urllib2、time、socket、threading、requests等模块实现延迟注入的方法。该工具可以通过构造特定的URL来进行注入测试,并通过延迟时间来判断注入是否成功。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
author-avatar
用户7kxpkjs2ol
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有