• 关于http请求、文件处理以及空间地理信息处理的工具类介绍


    一.简介

           本篇主要是搜集了日常开发中比较常用的几个工具类,他们均以Java实现,包含了比较常用的方法。比如Java实现http常见的get和post请求,Java实现文件内容读写,Java实现处理空间地理信息数据,比如合并四维空间数据的处理等。

            将这些工具类记录下来,便于以后涉及类似的场景和业务,可以直接取用和参考,也利于日后不断的改进和优化。

    二.HttpClientUtil工具类

    1. package com.sina.weibo.util;
    2. import java.io.*;
    3. import java.net.HttpURLConnection;
    4. import java.net.URL;
    5. /**
    6. * @classname HttpClientUtil
    7. * @author bingqing5
    8. * @date 2022/08/04 17:16
    9. * @version 2.0
    10. */
    11. public class HttpClientUtil {
    12. /**
    13. * 以get方式发起http请求,调用接口,获取数据
    14. *
    15. * @param httpUrl URL参数
    16. * @return 返回调用接口获取的字符串数据
    17. */
    18. public static String doGet(String httpUrl) {
    19. // 结果字符串
    20. String result = "";
    21. try {
    22. // 创建远程url连接对象
    23. URL url = new URL(httpUrl);
    24. StringBuilder document = new StringBuilder();
    25. // 将远程url转换为http远程连接类
    26. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    27. // 设置请求方式:get
    28. conn.setRequestMethod("GET");
    29. conn.setDoOutput(true);
    30. conn.setDoInput(true);
    31. // 设置URLConnection的连接参数
    32. conn.setRequestProperty("accept", "*/*");
    33. conn.setRequestProperty("connection", "keep-Alive");
    34. // 设置连接主机服务器的超时时间
    35. conn.setConnectTimeout(15000);
    36. // 设置读取远程返回的数据时间:60000毫秒
    37. conn.setReadTimeout(60000);
    38. InputStream input = null;
    39. try {
    40. // 建立远程连接
    41. conn.connect();
    42. input = conn.getInputStream();
    43. } catch (Exception e) {
    44. e.printStackTrace();
    45. }
    46. BufferedReader reader = new BufferedReader(new InputStreamReader(input,"utf-8"));
    47. String str = "";
    48. while ((str = reader.readLine()) != null) {
    49. document.append(str);
    50. }
    51. result = document.toString();
    52. reader.close();
    53. // 释放资源
    54. input.close();
    55. // 关闭连接
    56. conn.disconnect();
    57. } catch (Exception e) {
    58. e.printStackTrace();
    59. }
    60. return result;
    61. }
    62. /**
    63. * 以post方式发起请求,调用接口,获取数据
    64. * @param url 接口链接
    65. * @param param 请求参数
    66. * @return 以字符串形式返回接口获取的数据
    67. */
    68. public static String doPost(String url, String param) {
    69. PrintWriter out = null;
    70. BufferedReader in = null;
    71. String result = "";
    72. try {
    73. URL realUrl = new URL(url);
    74. // 将远程url转换为http远程连接类
    75. HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
    76. // 设置请求属性
    77. conn.setRequestProperty("accept", "*/*");
    78. conn.setRequestProperty("connection", "keep-Alive");
    79. conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
    80. conn.setRequestProperty("Charset", "UTF-8");
    81. // 设置URL请求方式
    82. conn.setRequestMethod("POST");
    83. // 使用URL进行输出
    84. conn.setDoOutput(true);
    85. // 使用URL进行输入
    86. conn.setDoInput(true);
    87. // 建立输出流,并写入数据
    88. out = new PrintWriter(conn.getOutputStream());
    89. out.print(param);
    90. out.flush();
    91. in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    92. String line;
    93. while ((line = in.readLine()) != null) {
    94. result += line;
    95. }
    96. } catch (Exception e) {
    97. e.printStackTrace();
    98. }
    99. finally {
    100. try {
    101. if (out != null) {
    102. out.close();
    103. }
    104. if (in != null) {
    105. in.close();
    106. }
    107. } catch (IOException e) {
    108. e.printStackTrace();
    109. }
    110. }
    111. return result;
    112. }
    113. }

          以上HttpClientUtil类提供了两个方法:doGet()和doPost()基本满足用Java实现调用第三方提供的接口做数据处理的场景。本人在日常工作中使用这两个方法去调用PHP后台接口,获取的数据一般是JSON字符串。经历了不知道多少次的调用,这两个方法都工作的很好。

    三.FileUtils工具类

    1. package com.sina.weibo.util;
    2. import java.io.*;
    3. import java.util.ArrayList;
    4. import java.util.List;
    5. /**
    6. * {@code FileUtils}是文件处理工具类。它提供了多种方法用于处理文件
    7. * 的读取和写入。
    8. *
    9. * @className FileUtils
    10. * @author bingqing5
    11. * @date 2022/03/07 11:30
    12. * @version 2.0
    13. */
    14. public class FileUtils {
    15. /**
    16. * 文件内容追加,以换行符结尾
    17. *
    18. * @param fileName 文件名(绝对文件路径名)
    19. * @param content 文件内容
    20. */
    21. public static void appendInfoToFile(String fileName, String content) {
    22. File file = new File(fileName);
    23. try {
    24. // 如果文件不存在,新建一个文件
    25. if (!file.exists()) {
    26. file.createNewFile();
    27. }
    28. OutputStreamWriter fileWriter = new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8");
    29. content = content + "\r\n";
    30. fileWriter.write(content);
    31. fileWriter.flush();
    32. fileWriter.close();
    33. } catch (IOException e) {
    34. e.printStackTrace();
    35. }
    36. }
    37. /**
    38. * 文件内容读取,将内容存入集合中返回
    39. *
    40. * @param fileName 文件名(绝对文件路径名)
    41. * @return 返回字符串list集合
    42. */
    43. public static List readInfoFromFile(String fileName) {
    44. File file = new File(fileName);
    45. if (!file.exists()) {
    46. return null;
    47. }
    48. List list = new ArrayList();
    49. try {
    50. BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
    51. String str = null;
    52. while (null != (str = bufferedReader.readLine())) {
    53. list.add(str);
    54. }
    55. bufferedReader.close();
    56. } catch (IOException e) {
    57. e.printStackTrace();
    58. }
    59. return list;
    60. }
    61. }

          以上FileUtils工具类提供了两个方法:appendInfoToFile()和readInfoFromFile(),分别用于将数据写入文件和将数据文件中读取存放到list集合中。需要注意的时,实践中,如果数据量达上亿条,1个G以上,读取时间会稍微拉长,达一分钟以上。当然,也并不一定要将数据存入list集合中再进行处理,可以读取一行数据以后及时处理,再读取下一行。但是往往在数据量很大的时候,处理数据需要关注一个问题:那就是如何防止数据重复处理,特别是在程序运行过程中被中断以后。将数据读取一遍存入list集合中个,诚然会消耗额外的内存资源,但与此同时也可以使用hashSet或LinkedHashSet存放已处理数据,在读取过程中与数据源集合中的数据对比,以便于达到避免重复处理相同的数据带来的开销。

          readInfoFromFile()方法只是针对特定文件目录下的单一文件,现实中也有很多场景需要读取文件目录下同类型的所有文件,这就需要去扫描文件目录下后缀相同的文件。以下方法,提供了实现:

    1. package siweiAutoUpdate.parse;
    2. import java.io.File;
    3. import java.io.FileInputStream;
    4. import java.io.FileOutputStream;
    5. import java.io.InputStream;
    6. import java.io.InputStreamReader;
    7. import java.io.Reader;
    8. import java.util.ArrayList;
    9. import java.util.HashMap;
    10. import java.util.HashSet;
    11. import java.util.List;
    12. import java.util.Map;
    13. import java.util.Set;
    14. public class ParseSiweiBaseData {
    15. private static List filePNameList = new ArrayList();
    16. private static List filePOIList = new ArrayList();
    17. private static List fileRelationList = new ArrayList();
    18. private static void scanPNameFile(File file) {
    19. if (file.isDirectory()) {
    20. File[] files = file.listFiles(new FileFilter() {
    21. public boolean accept(File pathname) {
    22. return pathname.isDirectory()
    23. || (pathname.getPath().endsWith(".mid") && pathname.getName().startsWith("PName"));
    24. }
    25. });
    26. for (File one : files) {
    27. scanPNameFile(one);
    28. }
    29. } else {
    30. filePNameList.add(file.getAbsolutePath());
    31. }
    32. }
    33. private static void scanPOIFile(File file) {
    34. if (file.isDirectory()) {
    35. File[] files = file.listFiles(new FileFilter() {
    36. public boolean accept(File pathname) {
    37. return pathname.isDirectory() || (pathname.getPath().endsWith(".mid")
    38. && !pathname.getName().startsWith("POI_") && pathname.getName().startsWith("POI"));
    39. }
    40. });
    41. for (File one : files) {
    42. scanPOIFile(one);
    43. }
    44. } else {
    45. filePOIList.add(file.getAbsolutePath());
    46. }
    47. }
    48. private static void scanRelationFile(File file) {
    49. if (file.isDirectory()) {
    50. File[] files = file.listFiles(new FileFilter() {
    51. public boolean accept(File pathname) {
    52. return pathname.isDirectory()
    53. || (pathname.getPath().endsWith(".mid") && pathname.getName().startsWith("POI_Relation"));
    54. }
    55. });
    56. for (File one : files) {
    57. scanRelationFile(one);
    58. }
    59. } else {
    60. fileRelationList.add(file.getAbsolutePath());
    61. }
    62. }
    63. }

    四.GeoUtils和Wkt类

          GetoUtils类相关依赖:

    1. <dependency>
    2. <groupId>com.vividsolutionsgroupId>
    3. <artifactId>jtsartifactId>
    4. <version>1.8version>
    5. dependency>

         代码如下:

    1. package com.sina.weibo.util;
    2. import com.vividsolutions.jts.geom.*;
    3. import com.vividsolutions.jts.io.ParseException;
    4. import com.vividsolutions.jts.io.WKTReader;
    5. import java.io.*;
    6. import java.text.DecimalFormat;
    7. import java.util.zip.GZIPInputStream;
    8. public class GeoUtils {
    9. public static final ThreadLocal FORMAT_LNGLAT = new ThreadLocal(){
    10. @Override
    11. protected DecimalFormat initialValue() {
    12. // TODO Auto-generated method stub
    13. return new DecimalFormat("###.00000");
    14. }
    15. };
    16. /**
    17. * 创建WGS84的GeometryFactory
    18. *
    19. * WGS-84 地心坐标系 4326
    20. * GCJ-02火星坐标系 3857
    21. *
    22. *
    23. * 地理坐标系:GCS_WGS_1984:WKID: 4326 权限: EPSG
    24. 投影坐标系:WGS_1984_Web_Mercator_Auxiliary_Sphere:WKID: 3857 权限: EPSG
    25. *
    26. *
    27. */
    28. static final GeometryFactory geometryFactory = new GeometryFactory(
    29. //new PrecisionModel(PrecisionModel.FLOATING), 4326);
    30. new PrecisionModel(PrecisionModel.FLOATING), 3857);
    31. /**
    32. * 获得WGS84的GeometryFactory
    33. * @return WGS84的GeometryFactory
    34. */
    35. public static final GeometryFactory getGeometryFactory() {
    36. return geometryFactory;
    37. }
    38. public static double formatLngLat(double lnglat){
    39. return Double.valueOf(FORMAT_LNGLAT.get().format(lnglat));
    40. }
    41. /**
    42. * 根据WKT生成MultiPolygon
    43. * @param wktPolygon 多边形的WKT
    44. * @return WKT生成MultiPolygon
    45. * @throws ParseException 生成MultiPolygon时发生异常
    46. */
    47. public static final MultiPolygon getMultiPolygon(String wktPolygon) {
    48. String wktPolygonN=null;
    49. try{
    50. if(wktPolygon.toUpperCase().startsWith("POLYGON")){
    51. wktPolygonN=wktPolygon.replace("POLYGON", "MULTIPOLYGON").replace("((", "(((").replace("))", ")))");
    52. }else{
    53. wktPolygonN=wktPolygon;
    54. }
    55. WKTReader reader = new WKTReader(geometryFactory);
    56. return (MultiPolygon) reader.read(wktPolygonN);
    57. }catch(Exception e){
    58. e.printStackTrace();
    59. }
    60. return null;
    61. }
    62. /**
    63. * 通过经纬度点生成多边形对象
    64. * @param ordinates 经纬度点,格式:经度,纬度,经度,纬度...
    65. * @return 生成的多边形对象
    66. */
    67. public static final Polygon getPolygon(double[] ordinates) {
    68. if (ordinates.length % 2 == 1)
    69. throw new RuntimeException("ordinates.length = " + ordinates.length + " error!");
    70. int lnglatLength = ordinates.length / 2;
    71. // 保证首位经纬度相同
    72. Coordinate[] coordinates = null;
    73. if (ordinates[0] == ordinates[ordinates.length - 2] && ordinates[1] == ordinates[ordinates.length -1]) {
    74. coordinates = new Coordinate[lnglatLength];
    75. } else {
    76. coordinates = new Coordinate[lnglatLength + 1];
    77. coordinates[coordinates.length - 1] = new Coordinate(ordinates[0], ordinates[1]);
    78. }
    79. for (int i = 0; i < lnglatLength; i++) {
    80. coordinates[i] = new Coordinate(ordinates[i*2],ordinates[i*2+1]);
    81. }
    82. LinearRing lr = geometryFactory.createLinearRing(coordinates);
    83. return geometryFactory.createPolygon(lr, null);
    84. }
    85. /**
    86. * 通过经纬度点生成路线
    87. * @param ordinates 经纬度点,格式:经度,纬度,经度,纬度...
    88. * @return 生成的路线
    89. */
    90. public static final LineString getLineString(double[] ordinates) {
    91. if (ordinates.length % 2 == 1)
    92. throw new RuntimeException("ordinates.length = " + ordinates.length + " error!");
    93. Coordinate[] coordinates = new Coordinate[ordinates.length / 2];
    94. for (int i = 0; i < coordinates.length; i++) {
    95. coordinates[i] = new Coordinate(ordinates[i*2],ordinates[i*2+1]);
    96. }
    97. return geometryFactory.createLineString(coordinates);
    98. }
    99. /**
    100. * 通过经纬度点生成多边形对象
    101. * @param coordinates 经纬度点
    102. * @return 生成的多边形对象
    103. */
    104. public static final Polygon getPolygon(Coordinate[] coordinates) {
    105. // 保证首位经纬度相同
    106. Coordinate[] lrCoordinates = null;
    107. if (coordinates[0].x == coordinates[coordinates.length -1].x && coordinates[0].y == coordinates[coordinates.length -1].y) {
    108. lrCoordinates = coordinates;
    109. } else {
    110. lrCoordinates = new Coordinate[coordinates.length + 1];
    111. for (int i = 0; i < coordinates.length; i++) {
    112. lrCoordinates[i] = coordinates[i];
    113. }
    114. lrCoordinates[lrCoordinates.length - 1] = coordinates[0];
    115. }
    116. LinearRing lr = geometryFactory.createLinearRing(lrCoordinates);
    117. return geometryFactory.createPolygon(lr, null);
    118. }
    119. /**
    120. * 根据WKT生成空间对象
    121. * @param 空间对象的类
    122. * @param wkt WKT
    123. * @param t 空间对象的实例
    124. * @return WKT生成空间对象
    125. * @throws ParseException 生成MultiPolygon时发生异常
    126. */
    127. public static final T getGeometry(final String wkt, final Class t) throws ParseException {
    128. WKTReader reader = new WKTReader(geometryFactory);
    129. return (T) reader.read(wkt);
    130. }
    131. /**
    132. * 根据WKT生成MultiLineString
    133. * @param wktLineString WKT
    134. * @return 生成的MultiLineString
    135. * @throws ParseException 生成MultiLineString时发生异常
    136. */
    137. public static final MultiLineString getMultiLineString(String wktLineString) throws ParseException {
    138. WKTReader reader = new WKTReader(geometryFactory);
    139. return (MultiLineString) reader.read(wktLineString);
    140. }
    141. /**
    142. * 地球赤道半径,单位米
    143. */
    144. public static final double EARTH_RADIUS = 6378137;
    145. /**
    146. * 面积(平方米)
    147. *
    148. */
    149. public static double getArea(Geometry wktGeo){
    150. double km=wktGeo.getArea()*10000;//平方公里
    151. return km*1000000;
    152. }
    153. /**
    154. * 距离(米)
    155. *
    156. */
    157. public static long getDistance(Geometry geometry1,Geometry geometry2){
    158. long distance=(long)(geometry1.distance(geometry2)*100000);
    159. return distance;
    160. }
    161. /**
    162. * 角度转弧度
    163. *
    164. * */
    165. public static double angleToRad(double angle) {
    166. return angle * Math.PI / 180.0;
    167. }
    168. /**
    169. * 弧度转角度
    170. *
    171. * */
    172. public static double radToAngle(double rad) {
    173. return 180.0/Math.PI * rad;
    174. }
    175. /**
    176. * 计算两个空间坐标点之间的距离,单位米
    177. * @param lng1 空间坐标点1的经度,单位度
    178. * @param lat1 空间坐标点1的纬度,单位度
    179. * @param lng2 空间坐标点2的经度,单位度
    180. * @param lat2 空间坐标点2的纬度,单位度
    181. * @return 两个空间坐标点之间的距离,单位米
    182. */
    183. public static double computeDistance(double lng1, double lat1, double lng2, double lat2) {
    184. final double radLat1 = angleToRad(lat1);
    185. final double radLat2 = angleToRad(lat2);
    186. final double a = radLat1 - radLat2;
    187. final double b = angleToRad(lng1) - angleToRad(lng2);
    188. double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
    189. Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
    190. s = s * EARTH_RADIUS;
    191. //s = Math.round(s * 10000) / 10000;
    192. return s;
    193. }
    194. /**
    195. * 获得p2点相对于p1点的极坐标方向,方向范围[0,360)
    196. * @param lng1 p1点的经度,单位度
    197. * @param lat1 p1点的纬度,单位度
    198. * @param lng2 p2点的经度,单位度
    199. * @param lat2 p2点的纬度,单位度
    200. * @return 获得p2点相对于p1点的极坐标方向,方向范围[0,360)
    201. */
    202. public static double getDirection(double lng1, double lat1, double lng2, double lat2) {
    203. double a = (Math.atan2(lng2 - lng1, lat2 - lat1) * 180d / Math.PI);
    204. if (a < 0) {
    205. a += 360;
    206. }
    207. return a;
    208. }
    209. public static final double R=22.5;
    210. public static final double R_3=R*3;
    211. public static final double R_5=R*5;
    212. public static final double R_7=R*7;
    213. public static final double R_9=R*9;
    214. public static final double R_11=R*11;
    215. public static final double R_13=R*13;
    216. public static final double R_15=R*15;
    217. /**
    218. * 获得角度方向的空间描
    219. * 每个象限分为4部分(22.5)
    220. * @param dirValue 角度方向值
    221. * @param isFuzzy 是否为模糊描述,当为true时描述信息只表示北、东北、东、东南等八个方向;当
    222. * 为false时返回以南北为参考的具体角度信息(非西方的以东西为参考),如正北,
    223. * 北偏东30度等。
    224. * @return 获得角度方向的空间描述
    225. */
    226. public static String getDirectionDesc(int dirValue, boolean isFuzzy) {
    227. String str1 = "";
    228. dirValue %= 360;
    229. if (dirValue < 0) dirValue += 360;
    230. if (isFuzzy) {
    231. if (dirValue <= R || dirValue >= R_15) str1 = "北";
    232. else if (dirValue < R_3) str1 = "东北";
    233. else if (dirValue <= R_5) str1 = "东";
    234. else if (dirValue < R_7) str1 = "东南";
    235. else if (dirValue <= R_9) str1 = "南";
    236. else if (dirValue < R_11) str1 = "西南";
    237. else if (dirValue <= R_13) str1 = "西";
    238. else if (dirValue < R_15) str1 = "西北";
    239. } else {
    240. if(dirValue==0)str1="正北";
    241. else if(dirValue<90)str1="北偏东";
    242. else if(dirValue==90) str1="正东";
    243. else if(dirValue<180 && dirValue>90) str1="南偏东";
    244. else if(dirValue==180) str1="正南";
    245. else if(dirValue<270 && dirValue>180) str1="南偏西";
    246. else if(dirValue==270) str1="正西";
    247. else if(dirValue<360 && dirValue>270) str1="北偏西";
    248. }
    249. return str1;
    250. }
    251. /**
    252. * 将double型的数四舍五入成多少位的数,当length大于0时,表示四舍五入小数点后length位;当length等于0时,表示四舍五入
    253. * 到整数;当length小于0时,表示四舍五入到整数部分前-length位。
    254. * @param value 待转换的数值
    255. * @param length 截取长度
    256. * @return 四舍五入后的数值
    257. */
    258. public static String roundTo(double value, int length) {
    259. value *= Math.pow(10, length);
    260. long result = Math.round(value);
    261. double newValue = result * Math.pow(10, -length);
    262. if (length <= 0) {
    263. return String.valueOf((long) newValue);
    264. } else {
    265. String resultStr = String.valueOf(newValue);
    266. int pos = resultStr.indexOf(".");
    267. int newLength = resultStr.length() - pos - 1;
    268. for (int i = newLength; i < length; i++) {
    269. resultStr += "0";
    270. }
    271. return resultStr.substring(0, pos + 1 + length);
    272. }
    273. }
    274. public static String readGeoloc(byte[] columnBlob) throws IOException {
    275. ByteArrayInputStream bais=new ByteArrayInputStream(columnBlob);
    276. BufferedInputStream b=new BufferedInputStream(new GZIPInputStream(bais));
    277. BufferedReader br=new BufferedReader(new InputStreamReader(b));
    278. StringBuffer sb=new StringBuffer();
    279. String p=br.readLine();
    280. while(p!=null){
    281. sb.append(p);
    282. p=br.readLine();
    283. }
    284. br.close();
    285. b.close();
    286. bais.close();
    287. p=sb.toString();
    288. sb.delete(0, p.length());
    289. return p;
    290. }
    291. }

         Wkt类提供了合并wkt数据的方法,代码如下:

    1. package com.sina.weibo.process;
    2. import com.sina.weibo.entity.WktObject;
    3. import com.sina.weibo.util.FileUtils;
    4. import com.sina.weibo.util.GeoUtils;
    5. import com.vividsolutions.jts.geom.Geometry;
    6. import com.vividsolutions.jts.geom.MultiPolygon;
    7. import java.util.ArrayList;
    8. import java.util.HashMap;
    9. import java.util.List;
    10. import java.util.Map;
    11. import java.util.stream.Collectors;
    12. /**
    13. * @author bingqing5
    14. * @date 2022/08/03
    15. * @version 1.0
    16. */
    17. public class Wkt {
    18. /**
    19. * 合并wkt数据
    20. *
    21. * @param list wkt列表
    22. * @return wkt文本
    23. */
    24. public static String mergeWkt(List list) {
    25. Geometry multiPolygon = null;
    26. int mc = 0;
    27. for (int i = 0; i < list.size(); i++) {
    28. String w = list.get(i).toUpperCase();
    29. if (w.startsWith("POLYGON") || w.startsWith("MULTIPOLYGON")) {
    30. MultiPolygon multiPolygonTmp = GeoUtils.getMultiPolygon(w);
    31. mc++;
    32. if (multiPolygon == null) {
    33. multiPolygon = (Geometry) multiPolygonTmp;
    34. } else {
    35. multiPolygon = multiPolygon.union(multiPolygonTmp);
    36. }
    37. }
    38. }
    39. return multiPolygon.toText();
    40. }
    41. }

          该方法在运行时会消耗大量的内存资源,而且数据处理时间较长。这是需要注意的点。

  • 相关阅读:
    Fama-French三因子和五因子模型和Stata代码(内附原始数据)
    Windows 安装TVM 及各种报错解决!无GPU版本
    声明式HTTP客户端-Feign 使用入门详解
    线性代数学习笔记7-2:矩阵对角化、求矩阵的幂、求一阶差分方程和Fibonacci数列(特征值的应用)
    Go开源世界主流成熟ORM框架gorm实践分享
    C语言变量定义选择题
    Spring Bean 的一生
    GBASE 8A v953报错集锦51--非空列的数据加载
    Springboot毕设项目老来福平台682f5(java+VUE+Mybatis+Maven+Mysql)
    分析入门 Python 股票量化交易的原因
  • 原文地址:https://blog.csdn.net/lbq15735104044/article/details/126580198