-
Mybatis-Plus(mbp) 提供 Wrapper 极大的减少大家的写SQL 的时间,Wrapper 类封装了大量的逻辑方法 eq , gt , lt等,使得操作数据库的代码进一步简洁。 在代码中有更新数据的需求,发现 mbp 提供了 UpdateWrapper 类简化更新的操作。 继承自 BaseMapper 的自定以mapper的 update() 有这两个参数 /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 (set 条件值,可以为 null) * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) */ int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); 实体对象可以为null且为set条件值,说明有两种方法可以实现更新。 第一种: 将需要更新的字段,设置到entity 中 UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("name","shimin"); User user = new User(); user.setAge(18); Integer rows = userMapper.update(user, updateWrapper); 第二种: 可以将entity设置为 null ,将需要更新的字段设置到 UpdateWrapper 中 UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); updateWrapper.set("id","123")eq("name","shimin"); Integer rows = userMapper.update(null, updateWrapper); ———————————————— 原文链接:https://blog.csdn.net/mikelv01/article/details/123920873
-
SpringBoot导出下载csv文件 说明 工具类代码 Controller代码 说明 今天看到需求里面有个需要将数据导成csv文件的格式,这里记录下自己的实现过程。直接上后端代码,这里用的springboot框架实现。 工具类代码 工具类代码下面最下面有一个main方法可直接执行导出功能,你可以用这个测试数据看看效果。 package com.etone.project.utils; import org.apache.commons.beanutils.BeanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.*; public class CsvUtils { public static final Logger logger = LoggerFactory.getLogger(CsvUtils.class); /** * 生成为CVS文件 * * @param exportData * 源数据List * @param map * csv文件的列表头map * @param outPutPath * 文件路径 * @param fileName * 文件名称 * @return */ @SuppressWarnings("rawtypes") public static File createCSVFile(List exportData, LinkedHashMap map, String outPutPath, String fileName) { File csvFile = null; BufferedWriter csvFileOutputStream = null; try { File file = new File(outPutPath); if (!file.exists()) { file.mkdir(); } // 定义文件名格式并创建 csvFile = File.createTempFile(fileName, ".csv", new File(outPutPath)); logger.debug("csvFile:" + csvFile); // UTF-8使正确读取分隔符"," csvFileOutputStream = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(csvFile), "GBK"), 1024); logger.debug("csvFileOutputStream:" + csvFileOutputStream); // 写入文件头部 for (Iterator propertyIterator = map.entrySet().iterator(); propertyIterator .hasNext();) { java.util.Map.Entry propertyEntry = (java.util.Map.Entry) propertyIterator .next(); csvFileOutputStream .write("" + (String) propertyEntry.getValue() != null ? (String) propertyEntry .getValue() : "" + ""); if (propertyIterator.hasNext()) { csvFileOutputStream.write(","); } } csvFileOutputStream.newLine(); // 写入文件内容 for (Iterator iterator = exportData.iterator(); iterator.hasNext();) { Object row = (Object) iterator.next(); for (Iterator propertyIterator = map.entrySet().iterator(); propertyIterator .hasNext();) { java.util.Map.Entry propertyEntry = (java.util.Map.Entry) propertyIterator .next(); csvFileOutputStream.write((String) BeanUtils.getProperty( row, (String) propertyEntry.getKey())); if (propertyIterator.hasNext()) { csvFileOutputStream.write(","); } } if (iterator.hasNext()) { csvFileOutputStream.newLine(); } } csvFileOutputStream.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { csvFileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return csvFile; } /** * 下载文件 * * @param response * @param csvFilePath * 文件路径 * @param fileName * 文件名称 * @throws IOException */ public static void exportFile(HttpServletResponse response, String csvFilePath, String fileName) throws IOException { // response.setHeader("Content-type", "application-download"); FileInputStream in = null; OutputStream out = response.getOutputStream(); byte[] buffer = new byte[1024]; int len = 0; response.setContentType("text/csv;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); response.setCharacterEncoding("UTF-8"); try { in = new FileInputStream(csvFilePath); while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } out.flush(); } catch (FileNotFoundException e) { logger.error("获取文件错误!"); } finally { if (in != null) { try { in.close(); out.close(); } catch (Exception e) { throw new RuntimeException(e); } } } } /** * 导出 * * @param response * @param * @return * @throws IOException */ public static void downloadFile(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException { // 获取服务其上的文件名称 File file = new File(uri); String name = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip"; StringBuffer sb = new StringBuffer(); sb.append("attachment; filename=").append(name); response.setHeader("Expires", "0"); response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); response.setHeader("Pragma", "public"); response.setContentType("application/x-msdownload;charset=UTF-8"); response.setHeader("Content-Disposition", new String(sb.toString() .getBytes(), "ISO8859-1")); // 将此文件流写入到response输出流中 FileInputStream inputStream = new FileInputStream(file); OutputStream outputStream = response.getOutputStream(); byte[] buffer = new byte[1024]; int i = -1; while ((i = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, i); } outputStream.flush(); outputStream.close(); inputStream.close(); } /** * 删除该目录filePath下的所有文件 * * @param filePath * 文件目录路径 */ public static void deleteFiles(String filePath) { File file = new File(filePath); if (file.exists()) { File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isFile()) { files[i].delete(); } } } } /** * 删除单个文件 * * @param filePath * 文件目录路径 * @param fileName * 文件名称 */ public static void deleteFile(String filePath, String fileName) { File file = new File(filePath); if (file.exists()) { File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isFile()) { if (files[i].getName().equals(fileName)) { files[i].delete(); return; } } } } } /** * 下载csv文件 * @param path * @param response */ public static void download(String path, HttpServletResponse response) { try { // path是指欲下载的文件的路径。 File file = new File(path); // 取得文件名。 String filename = file.getName(); // 以流的形式下载文件。 InputStream fis = new BufferedInputStream(new FileInputStream(path)); byte[] buffer = new byte[fis.available()]; fis.read(buffer); fis.close(); // 清空response response.reset(); // 设置response的Header,给文件名进行utf-8编码,不然下载的时候文件名乱码不对 response.addHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode (filename, "UTF-8" )); response.addHeader("Content-Length", "" + file.length()); OutputStream toClient = new BufferedOutputStream( response.getOutputStream()); response.setContentType("application/vnd.ms-excel;charset=gb2312"); toClient.write(buffer); toClient.flush(); toClient.close(); } catch (IOException ex) { ex.printStackTrace(); } } /** * 测试数据 * * @param args */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static void main(String[] args) { List exportData = new ArrayList<Map>(); Map row1 = new LinkedHashMap<String, String>(); row1 = new LinkedHashMap<String, String>(); row1.put("1", "11"); row1.put("2", "12"); row1.put("3", "13"); row1.put("4", "14"); exportData.add(row1); row1.put("1", "11"); row1.put("2", "12"); row1.put("3", "13"); row1.put("4", "14"); exportData.add(row1); LinkedHashMap map = new LinkedHashMap(); map.put("1", "第一列"); map.put("2", "第二列"); map.put("3", "第三列"); map.put("4", "第四列"); String path = "E:/export/"; String fileName = "文件导出"; File file = CsvUtils.createCSVFile(exportData, map, path, fileName); String fileName2 = file.getName(); logger.debug("文件名称:" + fileName2); } } main方式执行后,数据样式如下,我这里用的wps打开的: Controller代码 @RestController @RequestMapping("/dtracejobinfo") public class DtraceJobInfoController { public static final Logger logger = LoggerFactory.getLogger(DtraceJobInfoController.class); @Autowired public IDtraceJobInfoManager iDtraceJobInfoManager; //导出图层为csv文件 @RequestMapping(value = "/exportDtraceJob", method = RequestMethod.POST) public Object exportDtraceJob(@RequestParam(value = "jobName", required = false) String jobName, HttpServletResponse response) { Map<String, Object> result = new HashMap<String, Object>(); result.put("status", "000"); result.put("message", "操作成功!"); try { if(StringUtils.isBlank(jobName)){ result.put("status", "999"); result.put("message", "操作失败,jobName值不能为空!"); return result; } QueryCriteria criteria = new QueryCriteria(); criteria.put("jobName", jobName); //根据任务名称查询出对应的file_md5的值,然后根据file_md5值去dtrace_job_file_info表里面查询出对应的值 Map<String, Object> dtraceJob = iDtraceJobInfoManager.getByDtraceJobName(criteria); System.out.println("=============="+dtraceJob.get("dtrace_file_id")); if(dtraceJob.get("dtrace_file_id")==null){ result.put("status", "999"); result.put("message", "操作失败,当前任务dtrace_file_id值为空,无法下载!"); return result; } criteria.put("fileMd5", dtraceJob.get("dtrace_file_id").toString()); //因架构差异这里返回的一般是一个对象 List<Map<String, Object>> list =iDtraceJobInfoManager.exportDtraceJob(criteria); //下面代码是重点,参考下面代码 //设置第一列列名 LinkedHashMap map = new LinkedHashMap(); map.put("1", "st_astext"); List exportData = new ArrayList<Map>(); Map row1 = new LinkedHashMap<String, String>(); for (Map<String, Object> mapList : list) { // 执行导出操作 //System.out.println("经纬度值="+mapList.get("st_astext").toString()); row1 = new LinkedHashMap<String, String>(); row1.put("1", mapList.get("st_astext").toString()); exportData.add(row1); } //设置导出路径,本地测试用这个地址 String path = "E:/export/"; //线上放到linux服务器的时候用下面地址 //String path = "/data/export/"; //设置导出文件名 String fileName = jobName; File file = CsvUtils.createCSVFile(exportData, map, path, fileName); String fileName2 = file.getName(); logger.debug("文件名称:" + fileName2); //下载到本地 CsvUtils.download(path+fileName2,response); } catch (Exception e) { logger.error("操作失败:", e); result.put("status", "999"); result.put("message", "操作失败!"); } return result; } } 代码执行完后结果如下展示: 这里将代码里面的post请求方式改成get方便你直接用浏览器测试 下载逻辑是先将文件导出后,然后对该文件进行下载。好了,代码实现就到这里面了。 ———————————————— 原文链接:https://blog.csdn.net/weixin_48040732/article/details/120222910
-
业务场景:用户发送下单请求,格式为xml格式,服务器接收数据完成下单,并返回结果给客户。请求格式:<request><head><sign></sign><time></time><msg></msg></head><body><productId></productId><count></count><price></price><user><userId></userId><name></name><mobil></mobil><email></email></user><extendInfos><extendInfo><city></city><province></province><district></district><address></address></extendInfo></extendInfos></body></request>这段请求报文是xml格式的,在后台我们可以使用 javax.xml.bind.annotation 这个包格式化数据。我们可以在做数据对应的dto是用@XmlElement注释标注对应节点比如:import java.io.Serializable;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlElement;import javax.xml.bind.annotation.XmlRootElement;import org.apache.commons.lang.builder.ToStringBuilder;import org.apache.commons.lang.builder.ToStringStyle;import com.juqitech.adapter.dto.ChannelDTO;@XmlRootElement(name = "request")@XmlAccessorType(XmlAccessType.NONE)public class RequestDTO implements Serializable {private static final long serialVersionUID = -1;@XmlElementprivate RequestHeaderDTO head;@XmlElementprivate RequestBodyDTO body;public RequestHeaderDTO getHead() {return head;}public void setHead(RequestHeaderDTO head) {this.head = head;}public RequestBodyDTO getBody() {return body;}public void setBody(RequestBodyDTO body) {this.body = body;}}@XmlRootElement(name = "request")可以定义在类上,代表根节点@XmlAccessorType(XmlAccessType.FIELD)表示使用这个类中的 private 非静态字段作为 XML 的序列化的属性或者元素。这里还可以选择属性,也就是使用 set/get 方法来序列化属性或者元素。关键在于在请求中有个extendInfos的array数组,一般我们都会用list来表示,所以在RequestBodyDTO中可以这么写:import java.io.Serializable;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlElement;import javax.xml.bind.annotation.XmlRootElement;import org.apache.commons.lang.builder.ToStringBuilder;import org.apache.commons.lang.builder.ToStringStyle;import com.juqitech.adapter.dto.ChannelDTO;@XmlRootElement(name = "body")@XmlAccessorType(XmlAccessType.NONE)public class RequestBodyDTO implements Serializable {private static final long serialVersionUID = -1;//....前面的属性省略@XmlElementWrapper(name="extendInfos")@XmlElement(name="extendInfo")private List<extendInfo> extendInfos;//。。其他的省略}@XmlRootElement(name = "extendInfo")@XmlAccessorType(XmlAccessType.NONE)public class extendInfo{@XmlElementprivate String city;@XmlElementprivate String province;@XmlElementprivate String district;//省略}@XmlElementWrapper 这个注释用来在外面在包一层,用于处理list集合原文链接:https://www.shuzhiduo.com/A/kvJ30bPA5g/
-
@Scope注解 @Scope注解的目的是用来调节作用域 @Scope("prototype")//多实例,IOC容器启动创建的时候,并不会创建对象放在容器在容器当中,当你需要的时候,需要从容器当中取该对象的时候,就会创建。 @Scope("singleton")//单实例 IOC容器启动的时候就会调用方法创建对象,以后每次获取都是从容器当中拿同一个对象(map当中)。 @Scope("request")//同一个请求创建一个实例 @Scope("session")//同一个session创建一个实例 ———————————————— 原文链接:https://blog.csdn.net/weixin_43732955/article/details/88931003
-
spring给我们提供了jdbc的统一封装,和其它的orm不同,它是对jdbc api的封装,相当于我们自己写的java bean,而没有对对象模型进行封装(比如将po封装为pojo)。它保持了jdbc数据对象的原始性,具有极大程度的灵活性,其性能也极高。 我们知道在进行增量操作的时候最好是使用PreparedStatement,这样会节省数据库服务器编译sql语句的成本。而Spring也提供了对增量操作的支持,而BatchPreparedStatementSetter接口则是其核心。 先看看BatchPreparedStatementSetter接口里都定义了些操作。 public interface BatchPreparedStatementSetter{ public int getBatchSize(); public void setValues(PreparedStatement ps,int i);} 该接口是被JdbcTemplate类使用的CallBack接口。批操作中的每个操作都使用相同的sql语句,而该接口则sql语句中的占位符设置具体的值。 public int getBatchSize();用来返回批次的大小 public void setValues(PreparedStatement ps,int i); 用来为PreparedStatement设值。 参数说明: ps:我们将要设值的PreparedStatement i:在这个批次中,正在执行操作的索引,从0算起。 以批量更新为例,我们要做的是先实现BatchPreparedStatementSetter接口,然后再调用JdbcTemplate的batchUpdate(sql,setter)操作,参数sql是预编译语句 ,setter是BatchPreparedStatementSetter的一个实例。下面是我写的一段代码: public void batchInsertRows(String sql,final List<Object[]> dataSet) throws Exception{ BatchPreparedStatementSetter setter=new BatchPreparedStatementSetter(){ public int getBatchSize(){ return dataSet.size(); } public void setValues(PreparedStatement ps,int i){ Object[] obj = dataSet.get(i); int nextId = getNextId(); try{ ps.setLong(1,nextId); ps.setLong(2,Integer.parseInt(obj[0].toString())); ps.setLong(3,Integer.parseInt(obj[1].toString())); ps.setString(4,(String)obj[2]);} catch(Exception e){ e.printStackTrace(); } } }; jdbcTemplate.batchUpdate(sql,setter); }原文链接:https://www.cnblogs.com/love-you-girl/p/4032679.html
-
常见的Slf4j日志打印有两种方式,分别为传统方式(LoggerFactory)和注解方式(@Slf4j)。一 :传统方式package com.tr.demo.controller;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.GetMapping;@RestController@RequestMapping("/Test")public class Test { private final static Logger logger = LoggerFactory.getLogger(Test.class); @GetMapping("/helloWorld") public void String sayHello(){ logger.info("hello Sfl4j + logback......"); }}二:注解方式(@Slf4j)pom中引入依赖 <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency>IDEA安装lombok插件使用package com.tr.demo.controller;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.GetMapping;@Slf4j@RestController@RequestMapping("/Test")public class Test { @GetMapping("/helloWorld") public void String sayHello(){ logger.info("hello Sfl4j + logback......"); }}
-
1.Java虚拟机 Java虚拟机的主要任务是装在class文件并且执行其中的字节码。Java虚拟机包含一个类装载器,它可以从程序和 API中装载class文件。Java API中只有程序执行时需要的那些类才会被装载。字节码由执行引擎来执行。不同的Java虚拟机中,执行引擎可能实现得非常不同。在由软件实现的虚拟机中,最简单的执行引擎就是一次性解释字节码。 另一种执行引擎更快,但是也更消耗内存,叫做“即时编译器(just-in-TIme compiler)”。在这种情况下,第一次被执行的字节码会被编译成本地机器代码。编译出的本地机器代码会被缓存,当方法以后被调用的时候可以重用。 第三种执行引擎是自适应优化器。在这种方法里,虚拟机开始的时候解释字节码,但是会监视运行中程序的活动,并且记录下使用最频繁的代码段。程序运行的时候,虚拟机只把那些活动最频繁的代码编译成本地代码,其他的代码由于使用得不是很频繁,继续保留为字节码-由虚拟机继续解释它们。 一个自适应的优化器可以使得Java虚拟机在80%~90%的时间里执行被优化过的本地代码,而只需要编译10%~20%的对性能有影响的代码。2.类装载器的体系结构 一个Java应用程序可以使用两种类装载器:“启动(bootstrap)”类装载器和用户定义的类装载器。启动类装载器(这是系统中唯一的)是Java虚拟机实现的一部分。启动类装载器通常使用某种默认方式从本地磁盘中装载类,包括Java API类(启动类装载器也被称为原始类装载器、系统类装载器或者默认类装载器)。 Java应用程序能够在运行时安装用户定义的类装载器,这种类装载器能够使用自定义的方式来装载类。例如,从网络下载class文件。尽管启动类装载器是虚拟机实现的本质部分,而用户定义的类装载器不是,但用户定义的类装载器能够用Java来编写,能够被编译成class文件,能够被虚拟机装载,还能够像其它对象一样实例化。3.Java class文件 Java class文件主要在平台无关性和网络移动性方面使Java更适合网络。它在平台无关性方面的任务是:为Java程序提供独立于底层主机平台的二进制形式的服务。这种途径途径打破了C或者C++等语言所遵循的传统,使用这些传统语言写的程序通常首先被编译,然后被连接成单独的、专门支持特定硬件平台和操作系统的二进制文件。 通常情况下,一个平台上的二进制可执行文件不能在其他平台上工作。而Java class文件时可以运行在任何支持Java虚拟机的硬件平台和操作系统上的二进制文件。 4.Java API Java API通过支持平台无关性和安全性,使得Java适应于网络应用。Java API是运行库的集合,它提供了一套访问主机系统资源的标准方法。运行Java程序时,虚拟机装载程序的class文件所使用的Java API class文件。所有被装载的class文件(包括从应用程序中和从Java API中提取的)和所有已经装载的动态库(包含本地方法)共同组成了在Java虚拟机上运行的整个程序。
-
一、安装篇jspSmartUpload是一个可浏览使用的全功能的文件上传浏览组件,适于嵌入执行上传下载操作的JSP文件中。该组件有以下几个特点:1、使用简单。在JSP文件中仅仅书写三五行JAVA代码就可以搞定文件的上传或下载,方便。2、能全程控制上传。利用jspSmartUpload组件提供的对象及其操作方法,可以获得全部上传文件的信息(包括文件名,大小,类型,扩展名,文件数据等),方便存取。3、能对上传的文件在大小、类型等方面做出限制。如此可以滤掉不符合要求的文件。4、下载灵活。仅写两行代码,就能把Web服务器变成文件服务器。不管文件在Web服务器的目录下或在其它任何目录下,都可以利用jspSmartUpload进行下载。5、能将文件上传到数据库中,也能将数据库中的数据下载下来。这种功能针对的是MYSQL数据库,因为不具有通用性,所以本文不准备举例介绍这种用法。jspSmartUpload组件可以从网站上自由下载,压缩包的名字是jspSmartUpload.zip。下载后,用浏览或浏览将其解压到Tomcat的webapps目录下(本文以Tomcat服务器为例进行介绍)。解压后,将webapps/jspsmartupload目录下的子目录Web-inf名字改为全大写的WEB-INF,这样一改jspSmartUpload类才能使用。因为Tomcat对文件名大小写敏感,它要求Web应用程序相关的类所在目录为WEB-INF,且必须是大写。接着重新启动Tomcat,这样就可以在JSP文件中使用jspSmartUpload组件了。注意,按上述方法安装后,只有webapps/jspsmartupload目录下的程序可以使用jspSmartUpload组件,如果想让Tomcat服务器的所有Web应用程序都能用它,必须做如下工作:1.进入命令行状态,将目录切换到Tomcat的webapps/jspsmartupload/WEB-INF目录下。2.运行JAR打包命令:jar cvf jspSmartUpload.jar com(也可以打开资源管理器,切换到当前目录,用WinZip将com目录下的所有文件压缩成jspSmartUpload.zip,然后将jspSmartUpload.zip换名为jspSmartUpload.jar文件即可。)3.将jspSmartUpload.jar拷贝到Tomcat的shared/lib目录下。二、相关类说明篇(一)File类这个类包装了一个上传文件的所有信息。通过它,可以得到上传文件的文件名、文件大小、扩展名、文件数据等信息。File类主要提供以下方法:1、saveAs作用:将文件换名另存。原型:public void saveAs(java.lang.String destFilePathName)或public void saveAs(java.lang.String destFilePathName, int optionSaveAs)其中,destFilePathName是另存的文件名,optionSaveAs是另存的选项,该选项有三个值,分别是SAVEAS_PHYSICAL,SAVEAS_VIRTUAL,SAVEAS_AUTO。SAVEAS_PHYSICAL表明以浏览的根目录为文件根目录另存文件,SAVEAS_VIRTUAL表明以Web应用程序的根目录为文件根目录另存文件,SAVEAS_AUTO则表示让组件决定,当Web应用程序的根目录存在另存文件的目录时,它会选择SAVEAS_VIRTUAL,否则会选择SAVEAS_PHYSICAL。例如,saveAs("/upload/sample.zip",SAVEAS_PHYSICAL)执行后若Web服务器安装在C盘,则另存的文件名实际是c:\upload\sample.zip。而saveAs("/upload/sample.zip",SAVEAS_VIRTUAL)执行后若Web应用程序的根目录是webapps/jspsmartupload,则另存的文件名实际是webapps/jspsmartupload/upload/sample.zip。saveAs("/upload/sample.zip",SAVEAS_AUTO)执行时若Web应用程序根目录下存在upload目录,则其效果同saveAs("/upload/sample.zip",SAVEAS_VIRTUAL),否则同saveAs("/upload/sample.zip",SAVEAS_PHYSICAL)。建议:对于Web程序的开发来说,最好使用SAVEAS_VIRTUAL,以便移植。2、isMissing作用:这个方法用于判断用户是否选择了文件,也即对应的表单项是否有值。选择了文件时,它返回false。未选文件时,它返回true。原型:public boolean isMissing()3、getFieldName作用:取HTML表单中对应于此上传文件的表单项的名字。原型:public String getFieldName()4、getFileName作用:取文件名(不含目录信息)原型:public String getFileName()5、getFilePathName作用:取文件全名(带目录)原型:public String getFilePathName6、getFileExt作用:取文件扩展名(后缀)原型:public String getFileExt()7、getSize作用:取文件长度(以字节计)原型:public int getSize()8、getBinaryData作用:取文件数据中指定位移处的一个字节,用于检测文件等处理。原型:public byte getBinaryData(int index)。其中,index表示位移,其值在0到getSize()-1之间。(二)、Files类这个类表示所有上传文件的集合,通过它可以得到上传文件的数目、大小等信息。有以下方法:1、getCount作用:取得上传文件的数目。原型:public int getCount()2、getFile作用:取得指定位移处的文件对象File(这是com.jspsmart.upload.File,不是java.io.File,注意区分)。原型:public File getFile(int index)。其中,index为指定位移,其值在0到getCount()-1之间。3、getSize作用:取得上传文件的总长度,可用于限制一次性上传的数据量大小。原型:public long getSize()4、getCollection作用:将所有上传文件对象以Collection的形式返回,以便其它应用程序引用,浏览上传文件信息。原型:public Collection getCollection()5、getEnumeration作用:将所有上传文件对象以Enumeration(枚举)的形式返回,以便其它应用程序浏览上传文件信息。原型:public Enumeration getEnumeration()(三)Request类这个类的功能等同于JSP内置的对象request。只所以提供这个类,是因为对于文件上传表单,通过request对象无法获得表单项的值,必须通过jspSmartUpload组件提供的Request对象来获取。该类提供如下方法:1、getParameter作用:获取指定参数之值。当参数不存在时,返回值为null。原型:public String getParameter(String name)。其中,name为参数的名字。2、getParameterValues作用:当一个参数可以有多个值时,用此方法来取其值。它返回的是一个字符串数组。当参数不存在时,返回值为null。原型:public String[] getParameterValues(String name)。其中,name为参数的名字。转载自https://bbs.16rd.com/thread-582366-1-1.html
-
概述 什么是泛型?泛型能解决什么问题? 泛型即参数化类型,会让你的程序更易读,也更安全。 在 Java 增加泛型特性前【JDK5 前】,泛型的程序的设计是用继承来实现的。如下程序可以正常编译和运行,但将 get 的结果强制类型转换会产生一个错误。为了解决这个问题,泛型应运而生。 /** * 不同类型的元素add到ArrayList中 */ @Test public void mixedValueTest(){ ArrayList mixedValue = new ArrayList(); mixedValue.add(1L); mixedValue.add("string"); mixedValue.forEach(item->{ System.out.println((String)item); }); } // java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String 在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。 特性 泛型只在编译阶段有效,在编译时会采取去泛型化的措施:将泛型相关的信息擦除,同时在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行阶段。 /** * 使用类型参数约束元素的类型 */ @Test public void traitTest(){ ArrayList<String> stringList = new ArrayList<String>(); stringList.add("string a"); stringList.add("string b"); ArrayList<Integer> IntegerList = new ArrayList<Integer>(); IntegerList.add(100); IntegerList.add(200); if(stringList.getClass()==IntegerList.getClass()){ System.out.println("相同类型"); // 输出相同类型 } // 泛型类型在逻辑上可以看成是多个不同的类型,实际上都是相同的类型 } 泛型的定义和使用 泛型有三种使用方式:泛型类、泛型接口、泛型方法。 泛型类 泛型类型用于类的定义中,被称为泛型类,泛型类实例化时需要传入泛型类型实参【不传默认为 Object】。最典型的是各种容器类 List、ArrayList、Set、Map。 1. 泛型类的定义 class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{ private 泛型标识 变量名; ..... } } 常用的泛型标识【一般使用大写字母标识】: K/V/T/E/U/S/R...... 2. 泛型类的使用 // 泛型相当于普通类的工厂 类目<具体的数据类型> 对象名 = new 类名<具体的数据类型[JDK7后可省略]>(); 3. 从泛型类派生子类 // 子类也是泛型类,子类和父类的泛型类型要一致,只能在父类的基础上进行扩展【子类必须包含父类的泛型标识】 class 子类名<泛型标识:T> extends 父类名<泛型标识:T> // 子类不是泛型类,父类要明确泛型的数据类型 class 子类名 extends 父类名<明确的数据类型> 注意事项: 泛型的类型参数只能是 Object 类型,不能是基本数据类型 如果没有指定具体的数据类型。默认操作的类型是 Object 泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同的类型 泛型接口 泛型接口与泛型类的定义及使用基本相同。 1. 泛型接口的定义 interface 接口名称<泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{ private 泛型标识 方法名(); ..... } 2. 泛型接口的使用 // 实现类是泛型类,实现类和接口的泛型类型保持一致 class 实现类<T> implements 泛型接口<T> // 实现类不是泛型类,接口要明确数据类型 class 实现类 implements 泛型接口<String> 泛型方法 泛型方法:是在调用方法的时候指明泛型的具体类型,泛型方法中的泛型标识跟泛型类中的泛型标识无关,可以看成一个独立的体系。 1. 泛型方法定义 // 关键在于修饰符后的菱形语法<E> 修饰符 <E> 返回值类型 方法名(E ele){ return Objects.isNull(ele); } // 修饰符后的菱形语法<E>,可以理解声明此方法为泛型方法,泛型类的使用了泛型的成员方法并不是泛型方法。 2. 泛型方法和可变参数 public <E> void printElement(E... elements){ for(E element : elements){ System.out.println(element); } } 3. 泛型方法的使用 printElement("string",100,true); 小结: 泛型方法能是方法独立于类而产生变化 无论何时,如果能做到,就该尽量使用泛型方法,而不是将类泛型化 如果 static 方法要使用泛型能力,就必须将其定义为泛型方法,原因是静态方法无法访问类上定义的泛型。 通配符 泛型标识本质上也是通配符,没啥区别,是编码时约定俗成的东西。 通常情况下,T,E,K,V,? 是这样约定的: ? 表示不确定的 java 类型 T (type) 表示具体的一个 java 类型 K V (key value) 分别代表 java 键值中的 Key Value E (element) 代表 Element ? 和 T 的区别 T 是一个确定的类型,通常用于泛型类和泛型方法的定义,? 是一个不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。 通过 T 可以确定泛型参数的一致性 // ?本身就是不确定的元素,不能保证List的元素是一致的 void t1(List<? extends Number> list); // 确保泛型参数是一致的,List中存储相同的元素 void t1(List<T> list); 类型参数 T 可以多重限定,而通配符 ? 不行 通配符可以使用超类限定而类型参数不行 T extends Number // 允许 T susper Number // 不允许 ? extends Number // 允许 ? susper Number // 允许 通配符 <?> 无限定的通配符 无限定通配符经常与容器类配合使用,它其中的?其实代表的是未知类型,所以涉及到 ? 时的操作,一定与具体类型无关,只能操作与类型无关的方法: ArrayList<? super Number> arrayList3 = new ArrayList<>(); arrayList3.add(1); arrayList3.add(1.2); ArrayList<?> arrayList4 = new ArrayList<>(); // 将任意ArrayList加入ArrayList<?>不会出错 arrayList4 = arrayList3; // 操作arrayList4.add不能正常编译 // arrayList4.add(1); for (Object o : arrayList4) { // do something } 上限通配符 <? extends E> <? extends E> 限定参数类型的上限:参数类型必须是 E 或 E 的子类型: // 限定类型实参实现了Comparable接口,其中T表示限定类型的子类型,Comparable为限定类型的上界 public static <T extends Comparable> T min(T... comparbleElements) // 一个类型变量或通配符可以有多个限定 public static <T extends Comparable & Serializable> T min(T... comparableElements) 小结: 如果传入的类型不是限定类型及其子类,编译不成功 只能取,且只能取 E 和 E 的父类型 下限通配符 <? super E> <? super E> 限定参数类型的下界:参数类型必须是 E 或 E 的超类型。 susper 关键字和 extends 关键字功能相反,使用 <? super Integer> 通配符表示: 允许调用 set (? super Integer) 方法传入 Integer 的引用; 不允许调用 get () 方法获得 Integer 的引用。 对比 extends 和 super 通配符区别: <? extends T> 允许调用读方法 T get() 获取 T 的引用,但不允许调用写方法 set(T) 传入 T 的引用(传入 null 除外) <? super T> 允许调用写方法 set(T) 传入 T 的引用,但不允许调用读方法 T get() 获取 T 的引用(获取 Object 除外)。 一个是允许读不允许写,另一个是允许写不允许读。 类型擦除 泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。 在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T> 则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String> 则类型参数就被替换成类型上限。 无限制类型擦除 泛型标识未限定类型,编译器将未绑定的类型 T 替换为实际类型的 Object: public class Erasure<T>{ private T key; public T getKey() { return key; } public void setKey(T key) { this.key = key; } } // 类型擦除后 public class Erasure{ private Object key; public Object getKey() { return key; } public void setKey(Object key) { this.key = key; } } 有限制类型擦除 泛型标识有限定类型,编译器会将绑定类型参数 T 替换为第一个绑定类【存在多重限定的情况】: public class Erasure<T extends Number & Comparable>{ private T key; public T getKey() { return key; } public void setKey(T key) { this.key = key; } } // 类型擦除后 public class Erasure{ private Number key; public Number getKey() { return key; } public void setKey(Number key) { this.key = key; } } 桥接方法 一种允许扩展泛型类或实现泛型接口(带有具体类型参数)的类仍用作原始类型的方法。是编译器行为,在编译时自动在子类添加桥接方法以维持多态性。 编译器保护对桥接方法的访问,强制直接对其进行显式调用会导致编译时错误。 // 接口 interface IBridgeMethod<T> { public T info(T key); } // 泛型信息擦除后编译的字节码 interface IBridgeMethod { public Object info(Object key); } // 实现类 // 类型擦除后,方法签名不匹配,父类的info(Object key)方法不会被重写,为了解决这个问题并在类型擦除之后保留泛型类型的多态性,Java 编译器生成一个桥接方法来确保子类型按预期工作 public class BridgeMethodImpl implements IBridgeMethod<Integer>{ @Override public Integer info(Integer key) { return null; } } // 泛型信息擦除后编译的字节码:子类自动生成一个与父类的方法签名一致的桥接方法,可以通过反射或反编译看到 public class BridgeMethodImpl implements IBridgeMethod<Integer>{ public Integer info(Integer key) { return null; } // 编译器为了让子类有一个与父类的方法签名一致的方法,就在子类自动生成一个与父类的方法签名一致的桥接方法 @Override public Object info(Object key) { return info((Integer)key); } } 类型擦除带来的局限性 类型擦除,是泛型能够与之前的 java 版本代码兼容共存的原因。但也因为类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性。理解类型擦除有利于我们绕过开发当中可能遇到的雷区,同样理解类型擦除也能让我们绕过泛型本身的一些限制。比如: 正常情况下,因为泛型的限制,编译器不让最后一行代码编译通过,因为类似不匹配,但是,基于以上对类型擦除的了解,利用反射,我们可以绕过这个限制: @Test public void t4() throws Exception { ArrayList<Integer> arrayList2 = new ArrayList<>(); arrayList2.add(123); Method method = arrayList2.getClass().getDeclaredMethod("add",Object.class); method.invoke(arrayList2,"test"); System.out.println(arrayList2); //[123, test] } // 可以看到,利用类型擦除的原理,用反射的手段就绕过了正常开发中编译器不允许的操作限制 泛型的 PECS 原则 Producer extends 原则:当只想从容器中获取元素,请把这个容器看成生产者,使用 <? extends T> Consumer super 原则:当只想操作容器中的元素,请把这个容器看成消费者,使用 <? super T> ———————————————— 原文作者:Groot 转自链接:https://learnku.com/articles/64979
-
我们知道,Java标准库提供了一系列常用的哈希算法。 但如果我们要用的某种算法,Java标准库没有提供怎么办? 方法一:自己写一个,难度很大; 方法二:找一个现成的第三方库,直接使用。 BouncyCastle就是一个提供了很多哈希算法和加密算法的第三方库。它提供了Java标准库没有的一些算法,例如,RipeMD160哈希算法。 我们来看一下如何使用BouncyCastle这个第三方提供的算法。 首先,我们必须把BouncyCastle提供的jar包放到classpath中。这个jar包就是bcprov-jdk18on-xxx.jar,可以从官方网站下载。 Java标准库的java.security包提供了一种标准机制,允许第三方提供商无缝接入。我们要使用BouncyCastle提供的RipeMD160算法,需要先把BouncyCastle注册一下: ?010203040506070809101112131415161718192021publicclassMain{ publicstaticvoidmain(String[]args)throwsException{ //注册BouncyCastle: Security.addProvider(newBouncyCastleProvider()); //按名称正常调用: MessageDigestmd=MessageDigest.getInstance("RipeMD160"); md.update("HelloWorld".getBytes("UTF-8")); byte[]result=md.digest(); System.out.println(newBigInteger(1,result).toString(16)); } } 其中,注册BouncyCastle是通过下面的语句实现的: Security.addProvider(newBouncyCastleProvider()); 注册只需要在启动时进行一次,后续就可以使用BouncyCastle提供的所有哈希算法和加密算法。转载自https://bbs.tedu.cn/thread-368-1-1.html
-
Java编译器对String做了特殊处理,使得我们可以直接用+拼接字符串。 考察下面的循环代码: Strings=""; for(inti=0;i<1000;i++){ s=s+","+i; } 虽然可以直接拼接字符串,但是,在循环中,每次循环都会创建新的字符串对象,然后扔掉旧的字符串。这样,绝大部分字符串都是临时对象,不但浪费内存,还会影响GC效率。 为了能高效拼接字符串,Java标准库提供了StringBuilder,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象: StringBuildersb=newStringBuilder(1024); for(inti=0;i<1000;i++){ sb.append(','); sb.append(i); } Strings=sb.toString(); StringBuilder还可以进行链式操作: //链式操作 如果我们查看StringBuilder的源码,可以发现,进行链式操作的关键是,定义的append()方法会返回this,这样,就可以不断调用自身的其他方法。 仿照StringBuilder,我们也可以设计支持链式操作的类。例如,一个可以不断增加的计数器: //链式操作 注意:对于普通的字符串+操作,并不需要我们将其改写为StringBuilder,因为Java编译器在编译时就自动把多个连续的+操作编码为StringConcatFactory的操作。在运行期,StringConcatFactory会自动把字符串连接操作优化为数组复制或者StringBuilder操作。 你可能还听说过StringBuffer,这是Java早期的一个StringBuilder的线程安全版本,它通过同步来保证多个线程操作StringBuffer也是安全的,但是同步会带来执行速度的下降。 StringBuilder和StringBuffer接口完全相同,现在完全没有必要使用StringBuffer。 练习 请使用StringBuilder构造一个INSERT语句: publicclassMain{ publicstaticvoidmain(String[]args){ String[]fields={"name","position","salary"}; Stringtable="employee"; Stringinsert=buildInsertSql(table,fields); System.out.println(insert); Strings="INSERTINTOemployee(name,position,salary)VALUES(?,?,?)"; System.out.println(s.equals(insert)?"测试成功":"测试失败"); } } 小结 StringBuilder是可变对象,用来高效拼接字符串; StringBuilder可以支持链式操作,实现链式操作的关键是返回实例本身; StringBuffer是StringBuilder的线程安全版本,现在很少使用。 转载自https://bbs.tedu.cn/thread-380-1-1.html
-
JXL操作EXCEL的各个类的解析:Excel的生成方式其实很简单,主要包括几个大的类首先最重要的几个类就是WritableWorkbook,这个类代表的就是一个Excel文档,使用过Excel文档的都应该知道Excel文档的组成,都是由一个一个的sheet组成的,那么这个sheet对应的java类就是WritableSheet。实际上就是这两个大类的组成,其他的一些小的类都是为了修饰WritableSheet里的内容的。这些修饰类中用的最多的就是WritableFont ,WritableCellFormat,以及Label类,这三个类构成了修WritableSheet的核心。一、抽象类Workbook该类相当于是一个工具类用于生成Excel方法:(1) createWorkbook(File file)createWorkbook(File file,Workbook in)createWorkbook(java.io.OutputStream os)提供了各种形式的产生Excel的方式(2) findByName(String name)该方法的作用是获得名称为name的Range的数组(3) findCellByName(String name)该方法的作用是根据name来获取对应的cell对象(4) getCell(String location)该方法的作用是根据location来获得对应的cell对象(5)getNumberOfSheets() 该方法的作用是是获得sheet的总个数(6)getRangeNames() 该方法的作用是获得所有Range对象名称的数组(7)getSheet(int index) 该方法的作用是根据下标来获取对应的sheet对象(8)getSheet(String name) 该方法的作用是根据sheet的名称来获得对应的sheet对象(9)getSheetNames() 该方法的作用是获得所有sheet的名称所在的数组(10)getSheets() 该方法的作用是获得所有sheet对象所在的数组(11)getVersion() 该方法的作用是获得版本号(12)getWorkbook(File file) 该方法的作用就是获得一个Excel对象(13)getWorkbook(File file,Workbooksettings wss) 该方法的作用是获取一个Excel对象(14)isProtected() 该方法的作用是Excel是否是受保护的二、WritableWorkbook该类是一个实际的可写的Workbook(1)createSheet(java.lang.String name,int index) 该方法的作用是创建一个WorkSheet放在指定的位置(2)copy (Workbook book) 该方法的作用是复制WritableWorkbook(3)copySheet(int s,String name,int index) 该方法的作用是复制同一个workbook里处于s位置的sheet放到 新的位置index处(4)findByName(String name) 该方法的作用是获得名称为name的Range对象的数组(5)findCellByName(String name) 该方法的作用是获取名称为name的WritableCell对象(6)getNumberOfSheets() 该方法的作用是获得workbook里sheet的个数(7)getRangeNames() 该方法的作用是获得所有Range对象的名称所在的数组(8)getSheet(int index) 该方法的作用根据位置获取WritableSheet(9)getSheet(String name) 该方法的作用是根据名称来获去WritableSheet(10)getSheetNames() 该方法的作用是获得workbook里的所有sheet的名称所在的数组(11)getSheets() 该方法的作用是获得workbook里的所有的sheet所在的数组(12)importSheet(String name,int index, Sheet sheet)该方法的作用是从另外一个workbook里倒入一个sheet,name为新生成的sheet的名称,index为新的sheet在当前workbook里所处的位置(13)MOVeSheet(int from,int to)该方法的作用是移动一个sheet从from位置到to位置(14)removeRangeName(String name)该方法的作用是移除名称为name的Range对象(15)removeSheet(int index)该方法的作用是移除在index位置的sheet对象(16)setColorRGB(Colour c,int r,int g,int b)该方法的作用是个workbook设置一定的颜色(17)setOutputFile(File file)该方法的作用不太明确(18)setProtected(Boolean protected)该方法的作用是设置workbook是否为被保护(19)write()该方法非常重要,也就是生成一个Excel文档(20)close()该方法非常重要,在最后需要调用这个方法,才能使生成的Excel有效生成,否则,不写此句的话将会导致生成的Excel文件在打开时提示“文件的格式与扩展名指定的格式不一致”三、接口WritableSheet(1)addCell(WritableCell cell)该方法的作用是添加单元格需要注意的是一个Cell/Label只能使用一次(2)addColumnPageBreak(int break)该方法的作用不详(3)addHyperlink(WritableHyperlink link)该方法的作用是设置超链接(4)addImage(WritableImage image)该方法的作用是向Excel中添加图片对于JExcelAPI来说只支持对PNG图片的操作,而POI的功能则会更加强大一些(5)addRowPageBreak(int break)该方法的作用不详同addColumnPageBreak(int break)一样(6)getImage(int i)该方法的作用是获得sheet中的图片(7)getNumberOfImages()该方法的作用是获得sheet中的图片总数(8)getWritableCell(int column,int row) 该方法的作用是获得具体的某个单元格(9)getWritableHyperlinks() 该方法的作用是获得sheet里的所有超链接(10)insertColumn(int col) 该方法的作用是添加一列(11)insertRow(int row) 该方法的作用是添加一行(12)mergeCells(int col1,int row1,int col2,int row2) 该方法的作用是合并单元格 其中参数指的是需要合并的两个单元格所处的位置,也就是合并这两个单元格之间的所有单元格转载自https://bbs.16rd.com/thread-582303-1-1.html
-
步骤一:在idea中配置git.exeFile → Settings(Ctrl+Alt+S),点击Test出现Git版本,表示配置成功。步骤二:在idea中配置仓库信息步骤三:复制远程服务器上工程https连接步骤四:点击idea工具上传或者下载
-
应用安装后默认加入白名单, 白名单授权咋样申请
-
Set系列集合介绍 Set集合概述 Set系列集合特点: 无序:存取数据的顺序是不一定的, 当数据存入后, 集合的顺序就固定下来了 不重复:可以去除重复 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。 Set集合实现类特点: HashSet : 无序、不重复、无索引。 public static void main(String[] args) { // 无序, 不重复, 无索引 Set<String> sets = new HashSet<>(); sets.add("MySQL"); sets.add("MySQL"); sets.add("JAVA"); sets.add("JAVA"); sets.add("HTML"); sets.add("HTML"); sets.add("Vue"); sets.add("Vue"); System.out.println(sets); // [JAVA, MySQL, Vue, HTML] } LinkedHashSet:有序、不重复、无索引。 public static void main(String[] args) { // 有序、不重复、无索引 Set<String> sets = new LinkedHashSet<>(); sets.add("MySQL"); sets.add("MySQL"); sets.add("JAVA"); sets.add("JAVA"); sets.add("HTML"); sets.add("HTML"); sets.add("Vue"); sets.add("Vue"); System.out.println(sets); // [MySQL, JAVA, HTML, Vue] } TreeSet:排序: 默认升序、不重复、无索引。 public static void main(String[] args) { // 排序、不重复、无索引 Set<Integer> sets = new TreeSet<>(); sets.add(10); sets.add(10); sets.add(20); sets.add(20); sets.add(30); sets.add(30); sets.add(40); sets.add(40); sets.add(50); sets.add(50); System.out.println(sets); // [10, 20, 30, 40, 50] } Set集合的功能上基本上与Collection的API一致, Set集合没有扩展额外的API。 HashSet无序原理 HashSet集合底层采取哈希表存储的数据。 哈希表是一种对于增删改查数据性能都较好的结构。 哈希表的组成: JDK8之前的,底层使用数组+链表组成 JDK8开始后,底层采用数组+链表+红黑树组成。 哈希表是一种对于增删改查数据性能都较好的结构。 在了解哈希表之前需要先理解哈希值的概念 哈希值: 是JDK根据对象的地址,按照某种规则算出来的int类型的数值。 获取哈希值: 通过Object类的API: public int hashCode():返回对象的哈希值 对象的哈希值特点: 同一个对象多次调用hashCode()方法返回的哈希值是相同的 public static void main(String[] args) { String address = "成都市"; System.out.println(address.hashCode()); // 25299637 System.out.println(address.hashCode()); // 25299637 System.out.println(address.hashCode()); // 25299637 } 默认情况下,不同对象的哈希值是不同的。 public static void main(String[] args) { String address = "成都市"; System.out.println(address.hashCode()); // 25299637 System.out.println(address.hashCode()); // 25299637 System.out.println(address.hashCode()); // 25299637 String address2 = "重庆市"; System.out.println(address2.hashCode()); // 36643529 } JDK8之前的版本HashSet原理解析:数组 + 链表 +(结合哈希算法), 详细流程如下: 底层会默认创建一个默认长度16的数组,数组名table 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法) 例如数组长度是16, 哈希值与16取余, 得出的结果一定是0到15之间的数字 判断当前位置是否为null,如果是null直接存入 如果位置不为null,表示有元素,则调用equals方法比较 如果一样,则不存,如果不一样,则存入数组 在JDK 7中, 新元素占老元素位置,并且新元素会指向老元素 在JDK 8中, 新元素挂在老元素下面 JDK8之后的版本HashSet原理解析: 底层结构:哈希表(数组、链表、红黑树的结合体) 当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。 JDK8开始后,哈希表对于红黑树的引入进一步提高了操作数据的性能。 Set集合对象去重 HashSet去重注意点: Set集合在比较两个对象时, 默认比较的是两个对象的地址是否一致, 若地址不同则认为是两个不同的对象; 而如果希望Set集合认为2个内容一样的对象是重复的,则需要自己重写对象的hashCode()和equals()方法 我们来看下面这样一个案例: 需求: 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象 分析: 定义学生类,创建Set集合对象, 创建学生对象 把学生添加到集合 在学生类中重写两个方法,hashCode()和equals(),自动生成即可 步骤一: 定义学生类 public class Student { private String name; private int age; private int id; // 构造器 public Student() {}; public Student(String name, int age, int id) { this.name = name; this.age = age; this.id = id; } // getter和setter方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } // 重写toString方法 @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", id=" + id + '}'; } } 步骤二: 创建学生对象和HashSet集合, 并将学生对象存入HashSet集合中, 如下代码: public class Test { public static void main(String[] args) { // 创建集合存储学生对象 Set<Student> students = new HashSet<>(); // 创建学生对象 Student stu1 = new Student("小明", 18, 101); Student stu2 = new Student("小明", 18, 101); Student stu3 = new Student("小王", 20, 102); // 将学生对象添加到集合中 students.add(stu1); students.add(stu2); students.add(stu3); System.out.println(students); // 打印结果如下: // [Student{name='小明', age=18, id=101}, // Student{name='小明', age=18, id=101}, // Student{name='小王', age=20, id=102}] } } 步骤三: 我们发现步骤二代码中, stu1和stu2对象的内容完全一样, 但是由于对象的地址不一样, 会被当成两个不同的对象存入到集合中; 因此我们需要在学生类中重写两个方法,hashCode()和equals(),自动生成即可 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && id == student.id && Objects.equals(name, student.name); } @Override public int hashCode() { // 该方法传入的参数相同, 就会返回相同的哈希值 return Objects.hash(name, age, id); } 步骤四: 此时再将stu1和stu2对象存入集合, 由于内容一样就会被去掉重复的 public class Test { public static void main(String[] args) { // 创建集合存储学生对象 Set<Student> students = new HashSet<>(); // 创建学生对象 Student stu1 = new Student("小明", 18, 101); Student stu2 = new Student("小明", 18, 101); Student stu3 = new Student("小王", 20, 102); // 将学生对象添加到集合中 students.add(stu1); students.add(stu2); students.add(stu3); System.out.println(students); // [Student{name='小王', age=20, id=102}, Student{name='小明', age=18, id=101}] } } LinkedHashSet LinkedHashSet集合概述和特点: 有序、不重复、无索引。 这里的有序指的是保证存储和取出的元素顺序一致 原理: 底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。 TreeSet排序规则 TreeSet集合特点: 不重复、无索引、可排序 可排序:按照元素的大小默认升序(有小到大)排序。 TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。 注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。 TreeSet默认排序规则: 对于数值类型:Integer , Double,官方默认按照大小进行升序排序。 public static void main(String[] args) { Set<Integer> sets1 = new TreeSet<>(); sets1.add(50); sets1.add(10); sets1.add(30); sets1.add(20); System.out.println(sets1); // [10, 20, 30, 50] Set<Double> sets2 = new TreeSet<>(); sets2.add(10.11); sets2.add(20.22); sets2.add(43.22); sets2.add(8.22); System.out.println(sets2); // [8.22, 10.11, 20.22, 43.22] } 对于字符串类型:默认按照首字符的编号升序排序。 public static void main(String[] args) { Set<String> sets = new TreeSet<>(); sets.add("bbb"); sets.add("eee"); sets.add("aaa"); sets.add("ccc"); System.out.println(sets); // [aaa, bbb, ccc, eee] } 对于自定义类型如Student对象,TreeSet无法直接排序, 需要制定排序规则; 例如下面代码中向集合中添加学生类, TreeSet是无法进行排序的, 会崩溃报错。 // 错误演示 public static void main(String[] args) { // 创建学生对象 Student stu1 = new Student("小明", 18, 101); Student stu2 = new Student("小赵", 18, 102); Student stu3 = new Student("小王", 18, 103); // 创建集合 Set<Student> students = new TreeSet<>(); students.add(stu1); students.add(stu2); students.add(stu3); System.out.println(students); } 自定义排序规则: TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则 方式一: 让自定义的类(如学生类)实现Comparable接口, 并重写compareTo方法来定制比较规则。 // 实现Comparable接口 public class Student implements Comparable<Student> { // 其他代码... // 重写compareTo方法 @Override public int compareTo(Student o) { // 例如按照id进行排序 return this.id - o.id; } } public static void main(String[] args) { // 创建学生对象 Student stu1 = new Student("小明", 18, 101); Student stu2 = new Student("小赵", 18, 102); Student stu3 = new Student("小王", 18, 103); // 创建集合 Set<Student> students = new TreeSet<>(); students.add(stu1); students.add(stu2); students.add(stu3); System.out.println(students); // 打印结果: 按照id升序 // [Student{name='小明', age=18, id=101}, // Student{name='小赵', age=18, id=102}, // Student{name='小王', age=18, id=103}] } 方式二: TreeSet集合有参数构造器自带比较器对象,来进行定制比较规则, 并且该方法如果和方式一同时出现, 会优先使用此方法的比较规则。 public class SetDemo { public static void main(String[] args) { // 创建学生对象 Student stu1 = new Student("小明", 18, 101); Student stu2 = new Student("小赵", 18, 102); Student stu3 = new Student("小王", 18, 103); // 创建集合 // 方式二: 使用构造器自带的比较器对象 Set<Student> students = new TreeSet<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o2.getId() - o1.getId(); } }); students.add(stu1); students.add(stu2); students.add(stu3); System.out.println(students); // 打印结果: 按照id降序 // [Student{name='小王', age=18, id=103}, // Student{name='小赵', age=18, id=102}, // Student{name='小明', age=18, id=101}] } } 并且可以使用Lambda表达式简化代码 public class SetDemo { public static void main(String[] args) { // 创建学生对象 Student stu1 = new Student("小明", 18, 101); Student stu2 = new Student("小赵", 18, 102); Student stu3 = new Student("小王", 18, 103); // 创建集合 // 方式二: 使用构造器自带的比较器对象 Set<Student> students = new TreeSet<>((Student o1, Student o2) -> o2.getId() - o1.getId()); students.add(stu1); students.add(stu2); students.add(stu3); System.out.println(students); // 打印结果: 按照id降序 // [Student{name='小王', age=18, id=103}, // Student{name='小赵', age=18, id=102}, // Student{name='小明', age=18, id=101}] } } ———————————————— 版权声明:本文为CSDN博主「学全栈的灌汤包」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_71485750/article/details/127412828
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签