-
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
-
命令行 jps 查看正在运行的Java进程 ps(Java Process Status):显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。 说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。 基本使用语法为:jps [options] [hostid] 我们还可以通过追加参数,来打印额外的信息 options参数 -q:仅仅显示LVMID(local virtual machine id),即本地虚拟机唯一id。不显示主类的名称等 -l:输出应用程序主类的全类名 或 如果进程执行的是jar包,则输出jar完整路径 -m:输出虚拟机进程启动时传递给主类main()的参数 -v:列出虚拟机进程启动时的JVM参数。比如:-Xms20m -Xmx50m是启动程序指定的jvm参数。 说明:以上参数可以综合使用。 补充:如果某 Java 进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及下面介绍的jstat)将无法探知该Java 进程。 hostid参数 RMI注册表中注册的主机名。如果想要远程监控主机上的 java 程序,需要安装 jstatd。 对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击。 如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是在本地使用jstat和jps工具。 jstat:查看JVM统计信息 jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。 基本使用语法为:jstat - [-t] [-h] [ []] 查看命令相关参数:jstat-h 或 jstat-help 其中vmid是进程id号,也就是jps之后看到的前面的号码 option参数 选项option可以由以下值构成。 类装载相关的: -class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等 垃圾回收相关的: -gc:显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息。 -gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间。 -gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比。 -gccause:与-gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因。 -gcnew:显示新生代GC状况 -gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间 -geold:显示老年代GC状况 -gcoldcapacity:显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间 -gcpermcapacity:显示永久代使用到的最大、最小空间。 JIT相关的: -compiler:显示JIT编译器编译过的方法、耗时等信息 -printcompilation:输出已经被JIT编译的方法 jinfo:实时查看和修改JVM配置参数 jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。在很多情况卡,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便地找到Java虚拟机参数的当前值 基本使用语法为:jinfo [options] pid 说明:java 进程ID必须要加上 jmap:导出内存映像文件&内存使用情况 map(JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。开发人员可以在控制台中输入命令“jmap -help”查阅jmap工具的具体使用方式和一些标准选项配置。 基本使用语法为: jmap [option] jmap [option] <executable jmap [option] [server_id@] jhat:JDK自带堆分析工具 jhat(JVM Heap Analysis Tool):Sun JDK提供的jhat命令与jmap命令搭配使用,用于分析jmap生成的heap dump文件(堆转储快照)。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。 使用了jhat命令,就启动了一个http服务,端口是7000,即http://localhost:7000/,就可以在浏览器里分析。 说明:jhat命令在JDK9、JDK10中已经被删除,官方建议用VisualVM代替。 基本适用语法:jhat jstack:打印JVM中线程快照 jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。 生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。 在thread dump中,要留意下面几种状态 死锁,Deadlock(重点关注) 等待资源,Waiting on condition(重点关注) 等待获取监视器,Waiting on monitor entry(重点关注) 阻塞,Blocked(重点关注) 执行中,Runnable 暂停,Suspended 对象等待中,Object.wait() 或 TIMED_WAITING 停止,Parked jcmd:多功能命令行 在JDK 1.7以后,新增了一个命令行工具jcmd。它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。比如:用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。 cmd拥有jmap的大部分功能,并且在Oracle的官方网站上也推荐使用jcmd命令代jmap命令 jcmd 进程号 具体命令:显示指定进程的指令命令的数据 Thread.print 可以替换 jstack指令 GC.class_histogram 可以替换 jmap中的-histo操作 GC.heap_dump 可以替换 jmap中的-dump操作 GC.run 可以查看GC的执行情况 VM.uptime 可以查看程序的总执行时间,可以替换jstat指令中的-t操作 VM.system_properties 可以替换 jinfo -sysprops 进程id VM.flags 可以获取JVM的配置参数信息 GUI JDK自带的工具 jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等 Visual VM:Visual VM是一个工具,它提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。 JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据。 第三方工具 MAT:MAT(Memory Analyzer Tool)是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗 JProfiler:商业软件,需要付费。功能强大。 Jconsole jconsole:从Java5开始,在JDK中自带的java监控和管理控制台。用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。 Visual VM Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具。它集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。在JDK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisualVM 在JDK/bin目录下)即:它完全免费。 主要功能: 1.生成/读取堆内存/线程快照 2.查看JVM参数和系统属性 3.查看运行中的虚拟机进程 4.程序资源的实时监控 5.JMX代理连接、远程环境监控、CPU分析和内存分析 官方地址:https://visualvm.github.io/index.html Eclipse MAT MAT(Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况。MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便。 MAT可以分析heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息。一般说来,这些内存信息包含: 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。 所有的类信息,包括classloader、类名称、父类、静态变量等 GCRoot到所有的这些对象的引用路径 线程信息,包括线程的调用栈及此线程的线程局部变量(TLS) MAT 不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如Sun,HP,SAP 所采用的 HPROF 二进制堆存储文件,以及 IBM的 PHD 堆存储文件等都能被很好的解析。 最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现 官方地址: https://www.eclipse.org/mat/downloads.php JProfiler 在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。在eclipse里面有 Eclipse Memory Analyzer tool(MAT)插件可以测试,而在IDEA中也有这么一个插件,就是JProfiler。JProfiler 是由 ej-technologies 公司开发的一款 Java 应用性能诊断工具。功能强大,但是收费。 特点: 使用方便、界面操作友好(简单且强大) 对被分析的应用影响小(提供模板) CPU,Thread,Memory分析功能尤其强大 支持对jdbc,noSql,jsp,servlet,socket等进行分析 支持多种模式(离线,在线)的分析 支持监控本地、远程的JVM 跨平台,拥有多种操作系统的安装版本 主要功能: 1-方法调用:对方法调用的分析可以帮助您了解应用程序正在做什么,并找到提高其性能的方法 2-内存分配:通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄露问题,优化内存使用 3-线程和锁:JProfiler提供多种针对线程和锁的分析视图助您发现多线程问题 4-高级子系统:许多性能问题都发生在更高的语义级别上。例如,对于JDBC调用,您可能希望找出执行最慢的SQL语句。JProfiler支持对这些子系统进行集成分析 官网地址:https://www.ej-technologies.com/products/jprofiler/overview.html Arthas 上述工具都必须在服务端项目进程中配置相关的监控参数,然后工具通过远程连接到项目进程,获取相关的数据。这样就会带来一些不便,比如线上环境的网络是隔离的,本地的监控工具根本连不上线上环境。并且类似于Jprofiler这样的商业工具,是需要付费的。 那么有没有一款工具不需要远程连接,也不需要配置监控参数,同时也提供了丰富的性能监控数据呢? 阿里巴巴开源的性能分析神器Arthas应运而生。 Arthas是Alibaba开源的Java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。Arthas 支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! 是否有一个全局视角来查看系统的运行状况? 有什么办法可以监控到JVM的实时运行状态? 怎么快速定位应用的热点,生成火焰图? 官方地址:https://arthas.aliyun.com/doc/quick-start.html ———————————————— 版权声明:本文为CSDN博主「白马少年度春风」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_74787523/article/details/128274470
-
效果展示 一、游戏界面 二、悔棋与重开功能 项目介绍 五子棋是一种两人对弈的纯策略型棋类游戏,亦称串珠连,五子是中国民间非常熟知的一个古老棋种,它因操作简单、逻辑性强,深受喜爱,本项目基于Java技术,开发了一个操作简单、界面简洁、功能较完整的“五子棋”游戏。通过本游戏的开发,达到初学者学习和熟悉软件开发流程的目的。 总体需求 本程序主要完成五子棋游戏的简单操作,用户通过鼠标完成游戏过程。需要满足以下几点要求: 1.实现五子棋简易窗口界面。 2.实现黑白棋轮流下棋功能。 3.实现自动判断获胜方功能。 4.实现悔棋与重开游戏功能。 实现过程 1.绘制窗体对象。 2.UI设计(包括游戏区域、黑白棋子、按钮和标题区域)。 3.使用鼠标监听事件实现下棋。 4.连成五子判定获胜的实现。 代码展示 主框架 在主框架里有面板对象和两个按钮对象,分别是重新开始游戏按钮,悔棋按钮。还设置一个按钮事件类,用来监听两个按钮,并作出相应的动作,代码如下: import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; public class ChessJFrame extends JFrame { private ChessBord chessbord;//声明一个棋盘对象 private Panel tool; //声明一个面板对象 private Button StartButton;//声明开始按钮 private Button BackButton;//声明悔棋按钮 private Button exitButton;//声明退出按钮 public ChessJFrame() {//构造函数 setTitle("单机版五子棋");//设置标题 MyButtonLister mb=new MyButtonLister();//按钮事件处理对象 tool=new Panel();//面板对象 chessbord=new ChessBord();//棋盘对象 StartButton=new Button("Reopen");//设置开始按钮 BackButton=new Button("Regret");//设置悔棋按钮 //exitButton=new Button("EXIT");//设置退出游戏按钮 tool.setLayout(new FlowLayout(FlowLayout.CENTER ));//流式布局 tool.add(StartButton); tool.add(BackButton); // tool.add(exitButton);//将三个按钮添加到面板对象 StartButton.addActionListener(mb); BackButton.addActionListener(mb); // exitButton.addActionListener(mb);//将三个按钮添加到事件监听 add(tool,BorderLayout.SOUTH);//按钮所在的位置 add(chessbord);//添加棋盘对象 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭 pack();//自适应 } private class MyButtonLister implements ActionListener{ //按钮处理事件类 @Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub Object obj=e.getSource();//获取事件源 if(obj==StartButton) {//事件源是重新开始按钮 System.out.println("重新开始"); chessbord.restartGame(); } else if(obj==BackButton) {//事件源是悔棋按钮 System.out.println("悔棋!"); chessbord.goback(); } else if(obj==exitButton) {//事件源是退出按钮 System.exit(0); } } } public static void main(String[] args) { ChessJFrame jf=new ChessJFrame();//声明框架对象 jf.setLocationRelativeTo(null);//居中显示 jf.setVisible(true);//设置为可见 } } 棋子类 棋子类里有棋子的坐标,半径和颜色;还有棋子的构造函数。代码如下: import java.awt.Color; public class Chess { private int x;//棋子的x坐标索引 private int y;//棋子的y坐标索引 private Color color;//棋子颜色 public static int DIAMETER=30;//直径 public Chess(int x,int y,Color color){//棋子构造函数 this.x=x; this.y=y; this.color=color; } public int getX() { return x; } public int getY() { return y; } public Color getColor() { return color; } } 棋盘类 我们要对棋盘的边距,网格的距离进行赋值,设定棋盘的初始状态,如默认黑子先下,定义一个棋子类数组,来保存棋子对象,再定义一个字符串二维数组函数来保存颜色,用来判断输赢,用匿名内部类来处理当棋子在面板上移动时的一些状态。 再对下棋时鼠标按下时的动作事件类进行解析,当在网格上下棋时,判断是否在棋盘内和判断网格上有没有棋子,当棋子在棋盘内并且网格上没有棋子,我们就开始画棋子,将棋子对象和颜色保存,接下来判断是否胜利,判断棋盘是否下满,如果没有继续下棋。 使用函数paintComponent来对棋盘和棋子进行画图,对棋子进行绘图时,将最后一个棋子设为红色。 最后就是判断棋子输赢的函数win,用字符串数组保存的棋子的颜色来进行判断所在行和列有没有五个棋子相连,判断撇和捺行的棋子有没有五个相连的,如果有则某位棋子胜利。代码如下: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ChessBord extends JPanel implements MouseListener{//继承面板类和鼠标事件接口 public static int MARGIN=30;//定义边距 public static int ROWS=15;//定义行数 public static int COLS=15;//定义列数 public static int GRID_SPAN=35;//网格间距 Chess[] chessList=new Chess[(ROWS+1)*(COLS+1)];//定义一个棋子数组 String[][] board=new String[MARGIN*2+GRID_SPAN*COLS][MARGIN*2+GRID_SPAN*COLS];//声明一个字符串数组,用来判断输赢 int chessCount;//棋子数目 int xindex,yindex;//棋子的坐标索引 boolean start=true;//开始默认黑子先下 boolean GameOver=false;//定义是否游戏结束 public ChessBord() {//棋盘类构造函数 setBackground(Color.LIGHT_GRAY);//设置背景颜色 addMouseListener(this);//将棋盘类添加到鼠标事件监听器 addMouseMotionListener(new MouseMotionListener() {//匿名内部类 @Override public void mouseMoved(MouseEvent e) {//根据鼠标的移动所在的坐标来设置鼠标光标形状 int x1=(e.getX()-MARGIN+GRID_SPAN/2)/GRID_SPAN;//对鼠标光标的x坐标进行转换 int y1=(e.getY()-MARGIN+GRID_SPAN/2)/GRID_SPAN;//对鼠标光标的y坐标进行转换 if(x1<0||x1>ROWS||y1<0||y1>COLS||GameOver||findchess(x1, y1)) { setCursor(new Cursor(Cursor.DEFAULT_CURSOR));//设置鼠标光标为默认形状 }else { setCursor(new Cursor(Cursor.HAND_CURSOR));//设置鼠标光标为手型 } } @Override public void mouseDragged(MouseEvent e) { } }); for(int i=0;i<MARGIN*2+GRID_SPAN*COLS;i++) {//对board[][]赋初值 for (int j = 0; j < MARGIN*2+GRID_SPAN*COLS; j++) { board[i][j]="0"; } } } @Override public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mousePressed(MouseEvent e) {//鼠标点击事件 if(GameOver)//游戏结束,不能按 return ; String colorName=start?"黑棋":"白棋";//判断是什么颜色的棋子 xindex=(e.getX()-MARGIN+GRID_SPAN/2)/GRID_SPAN;//得到棋子x坐标 yindex=(e.getY()-MARGIN+GRID_SPAN/2)/GRID_SPAN;//得到棋子y坐标 board[xindex][yindex]=colorName;//以棋子x坐标y坐标做索引将棋子的颜色添加到board中 if(xindex<0||xindex>ROWS||yindex<0||yindex>COLS) {//棋子在棋盘外不能下, return ; }else if(findchess( xindex, yindex)) {//所下位置已有棋子,不能下 return ; } Chess po=new Chess(xindex,yindex,start?Color.black:Color.WHITE);//对棋子对象进行初始化 chessList[chessCount++]=po;//将棋子对象添加到棋子数组中 repaint();//重画图型 if(win( xindex,yindex,start)) {//判断是否胜利 String msg=String.format("恭喜 %s赢了",colorName); JOptionPane.showMessageDialog(this, msg); //gameOver=true; GameOver=true; }else if(chessCount==(COLS+1)*(ROWS+1)) {//判断是否全部下满 String msg=String.format("恭喜 %s赢了",colorName); JOptionPane.showMessageDialog(this, msg); GameOver=true; } start=!start;//改变棋子先下棋状态 } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override protected void paintComponent(Graphics g) {//画棋盘和棋子 super.paintComponent(g); for(int i=0;i<=ROWS;i++) {//画横线 g.drawLine(MARGIN, MARGIN+i*GRID_SPAN, MARGIN+COLS*GRID_SPAN, MARGIN+i*GRID_SPAN); } for(int j=0;j<=COLS;j++) {//画竖线 g.drawLine(MARGIN+j*GRID_SPAN, MARGIN, MARGIN+j*GRID_SPAN, MARGIN+ROWS*GRID_SPAN); } for(int i=0;i<chessCount;i++) {//画棋子 int xpos=chessList[i].getX()*GRID_SPAN+MARGIN;//得到棋子x坐标 int ypos=chessList[i].getY()*GRID_SPAN+MARGIN;//得到棋子y坐标 g.setColor(chessList[i].getColor());//设置棋子颜色 g.fillOval(xpos-Chess.DIAMETER/2, ypos-Chess.DIAMETER/2, Chess.DIAMETER, Chess.DIAMETER);//画棋子 if(i==chessCount-1){ g.setColor(Color.red);//标记最后一个棋子为红色 g.drawRect(xpos-Chess.DIAMETER/2, ypos-Chess.DIAMETER/2, Chess.DIAMETER, Chess.DIAMETER); } } } private boolean findchess(int index,int yindex) {//查找所在位置是否有棋子 for (Chess c : chessList) { if(c!=null&&c.getX()==xindex&&c.getY()==yindex) return true; } return false; } private boolean win(int x,int y,boolean start) {//对棋子输赢的判断 String str=start?"黑棋":"白棋"; //棋子所在行和列是否有五子相连的情况 for(int i=0;i<16;i++){ if((board[x][i].equals(str)&&board[x][i+1].equals(str)&&board[x][i+2].equals(str)&&board[x][i+3].equals(str)&&board[x][i+4].equals(str))||(board[i][y].equals(str)&&board[i+1][y].equals(str)&&board[i+2][y].equals(str)&&board[i+3][y].equals(str)&&board[i+4][y].equals(str))) return true; } //棋子所在撇行是否有五子相连的情况 if(x+y>=4&&x+y<=30){ int i=(x+y<=19)?x+y:x+y-20; if(x+y<=19){ for(int k=0;k<=i-4;k++){ if(board[k][i-k].equals(str)&&board[k+1][i-k-1].equals(str)&&board[k+2][i-k-2].equals(str)&&board[k+3][i-k-3].equals(str)&&board[k+4][i-k-4].equals(str)) return true; } }else{ for(int k=i;k<=15;k++){ if(board[k][20-k].equals(str)&&board[k+1][20-k-1].equals(str)&&board[k+2][20-k-2].equals(str)&&board[k+3][20-k-3].equals(str)&&board[k+4][20-k-4].equals(str)) return true; } } } //棋子所在捺行是否有五子相连的情况 if(y-x<=15&&x-y<=15){ int i=(x<y)?y-x:x-y; if(x<y){ for(int k=0;k<=19-4-i;k++){ if(board[k][i+k].equals(str)&&board[k+1][i+k+1].equals(str)&&board[k+2][i+k+2].equals(str)&&board[k+3][i+k+3].equals(str)&&board[k+4][i+k+4].equals(str)) return true; } }else{ for(int k=i;k<=15;k++){ if(board[k][i+k].equals(str)&&board[k+1][i+k+1].equals(str)&&board[k+2][i+k+2].equals(str)&&board[k+3][i+k+3].equals(str)&&board[k+4][i+k+4].equals(str)) return true; } } } return false; } public void goback() {//悔棋函数 if(chessCount==0) { return ; } chessList[chessCount-1]=null; chessCount--; if(chessCount>0) { xindex=chessList[chessCount-1].getX(); yindex=chessList[chessCount-1].getY(); } start=!start; repaint(); } public void restartGame() {//重新开始函数 for(int i=0;i<chessList.length;i++)//设置为初始状态 chessList[i]=null; for(int i=0;i<MARGIN*2+GRID_SPAN*COLS;i++) { for (int j = 0; j < MARGIN*2+GRID_SPAN*COLS; j++) { board[i][j]="0"; } } start=true; GameOver=false; chessCount=0; repaint(); } public Dimension getPreferredSize(){//画矩形 return new Dimension(MARGIN*2+GRID_SPAN*COLS,MARGIN*2+GRID_SPAN*ROWS); } } 项目结构 本程序用于Java初学者学习,共封装三个类,分别是主框架ChessJFrame类、棋子Chess类、棋盘ChessBord类,结构简单,框架明了。 总结 本项目的设计思路与“五子棋”游戏一致,程序在界面设计与功能实现上还有许多不足,但该项目的实现对于Java编程新手实战能力的提升有很大作用,在编写该项目的过程中,对编程者算法的设计能力与面向对象编程的理解都有深刻的考验。 程序在一定程序上还有还有缺陷,仅用于初学者学习,欢迎各位指正。 ———————————————— 版权声明:本文为CSDN博主「陈橘又青」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_63947499/article/details/125417438
-
一、基于代码智能生成API文档 众所周知,Java程序员在写完后端API接口后,都要和前端程序员进行接口调试,这个时候,就要手写API文档了,包括API名称、请求方式、入参、出参、一些注意事项等。 如果调试出问题了,需要调整代码,再调整API文档,苦不堪言。后来一个同事向我推荐了Eolink,可以直接基于代码生成API文档,这,太香了。 体验链接 ➢ ➢ ➢ https://www.eolink.com/ 1、点击其他,选择API文档生成 2、点击添加来源,在弹窗中选择通过 Swagger URL 生成 API 文档,然后点击下一步: 配置完成后,界面会提示配置完成。此时您可以通过在当前页面页点击同步按钮,或者通过 Open API 触发同步操作。 3、从代码仓库中扫描 API 文档 目前支持GitHub、Gitlab、码云。 进入Eolink主页,点击左下角其他,点击API文档生成,点击添加来源,即可看到代码仓库。 4、以GitHub为例,配置项介绍 •来源名称 •代码注释方式(swagger、apiDoc) •代码仓库地址 •用户名:GitHub账户名称 •仓库名:GitHub Repository 仓库名称 •访问私钥:仓库私人令牌,可在Personal Access Tokens中生成 •需要扫描的分支 •需要扫描的API目录路径:API 层相关代码的存放路径 •需要扫描的数据结构目录路径 •目标语言:java、php •数据同步方式:目前可选增量更新、全量更新、仅添加新的API三种形式,推荐采用增量更新的方式。每次同步之后,系统都会自动生成API历史版本方便回滚文档,因此不用担心操作失误问题。 可以根据不同的需求,选择适合自己的方式,直接生成API文档,解放了我们的双手,爽歪歪。 二、基于API文档智能生成测试用例 体验链接 ➢ ➢ ➢ https://www.eolink.com/ 通过Eolink的 自动生成测试用例 的功能,可以帮助测试人员根据预先定义的规则快速生成对应接口的测试用例,并可以在次基础上进行二次编辑调整,大大减少测试人员用例编写时间。 1、新建API 点击+API按钮,填写 API 的请求地址、请求参数、校验规则等信息,然后保存即可。 2、校验返回结果 测试用例支持对返回结果进行校验,以下是几种结果校验规则: 很多时候,测试需要经过一个漫长的过程,单元测试、前后端联调、系统测试、性能测试等等,一套API接口可能需要很多人、在不同的电脑上进行各种各样的测试,测试一次编写一次API路径、入参……麻烦至极!此时,Eolink又给大家提供了一个很便捷的功能,导出API文档、再到其它电脑导入API文档生成测试用例,让我们一起来体验一下吧。 3、导出API文档 4、导入api文档,生成测试用例 5、API文档导入成功,测试十分便捷 目前的项目基本都是前后端分离,后端为前端提供接口的同时,还需同时提供接口的说明文档。但我们的代码总是会根据实际情况来实时更新,这个时候有可能会忘记更新接口的说明文档,造成一些不必要的问题。 通过导出、导入功能,生成可视化 RESTful 风格的 Web 服务的接口文档,前后端工程师沟通起来更加便捷。 三、基于API文档智能生成请求代码和业务代码 1、单点测试功能齐全 •编辑 •查看、对比API的修改历史 •当API改变时通知相关成员 •对API文档发表评论并通知相关人员 •代码示例 •复制、删除 2、API请求代码支持多种语言 3、生成API请求代码 基于API文档智能生成请求代码和业务代码,可以查看API的修改历史,大大提高了代码的容错率和健壮性;还支持生成各种编程语言的代码示例,节省了大量的代码交接、讲解时间。 4、生成业务代码 登录私有云,点击API代码生成,根据需求,生成业务代码。 四、生成请求参数,前后置脚本,Mock规则等业务数据 1、快速生成随机参数值 点击构造器按钮,在弹窗中选择 动态生成数据,在下拉菜单中可以选择需要生成的随机值,每次请求的时候都可以动态生成不同的请求参数。 2、对参数进行快速加解密处理 例如需要对 user_password 字段值:123456 进行 MD5 加密。点击相应参数的 构造器 按钮: 在弹窗中选择需要处理的数据以及要进行的操作。选择 user_password 的参数值,并且选择 MD5 加密操作,然后点击替换即可: 构造完成之后会得到 user_password 经过 MD5 加密之后的结果: 3、编辑前置脚本 前置脚本都包括什么呢? 体验链接 ➢ ➢ ➢ https://www.eolink.com/ 4、编辑后置脚本 后置脚本都包括什么呢? 5、编辑 Mock API 期望 在编写API文档返回结果时,若参数字段和类型匹配智能Mock规则,系统则会自动填入对应的Mock值。该功能提供无感的快速mock值配置,减轻mock规则配置的工作负担。 智能Mock的匹配规则分两种类型:内置规则和自定义规则。 内置规则是一套完整的预置智能Mock规则,用户不可编辑具体的匹配规则,仅可整体启用或停用。内置规则有助于用户快速使用智能Mock能力。 自定义规则是用户自定义自由配置的智能Mock匹配规则,用户可以根据个性化需求创建、编辑和删除自定义规则。支持使用通配符、正则表达式、完全匹配的方式来匹配字段名。 (1)进入空间级公共资源内的智能mock设置,可看到自定义规则模块和内置规则模块。 (2)在内置规则模块右上角,有开启/停用内置规则的切换按钮。该按钮默认开启,点击按钮则工作空间内停用内置智能Mock规则。 (3)点击页面标题下的保存按钮,变更方可生效。 添加/编辑自定义规则 进入空间级公共资源内的智能mock设置页。 在自定义规则模块中,点击底部最后一行中输入对应的数据即可添加新的自定义规则: (4)字段规则说明 1.数据类型:匹配的字段数据类型,必填。 2.匹配方式:用于确认字段名称的匹配方式。默认为通配符,可选择通配符、正则表达式、完全匹配三种方式。 3.匹配规则:可输入字段名称匹配表达式,必填。根据所选匹配方式输入对应的名称匹配表达式。 4.匹配大小写:控制字段名称匹配时是否大小写敏感,默认为否。 5.Mock 规则:支持输入任意固定值或mockjs的占位符。当字段匹配智能Mock规则的数据类型和字段名称后,系统自动填入的mock值。 6.备注:用于标注说明该智能匹配规则,仅展示于智能mock设置页。 7.操作:当前仅提供删除操作。 完成自定义规则添加或编辑后,需点击页面标题下的保存按钮才会生效。否则刷新页面后当次编辑的数据会清空。 (5)删除自定义设置 进入空间级公共资源内的智能mock设置页。 点击对应自定义规则右侧的删除操作按钮,即可进行删除。 点击页面标题下的保存按钮,变更方可生效。 (6)规则优先级 智能Mock中,自定义规则的优先级大于内置规则。若某个字段同时满足自定义规则和内置规则的匹配条件,则该字段的mock规则优先填入自定义规则的mock内容。(即自定义规则>内置规则) 新创建的自定义规则优先级更高。若某个字段同时满足两个自定义规则的匹配条件,则该字段的mock规则优先填入较新创建的自定义规则的mock内容。 随着测试API的全面智能化,可以大大的提升测试阶段的工作效率;降低测试岗位的工作难度,进一步拓展职场人的能力边界,使得Java工程师也可以兼职测试工作,成就编程“多面手”。 五、总结 智能化测试是近年来最热门的软件测试话题,它已成为许多高级开发和部署实践的关键推动因素。本文通过简单的实例,介绍了基于代码智能生成API文档、基于API文档智能生成测试用例、基于API文档智能生成请求代码和业务代码、生成请求参数、前后置脚本、Mock规则等业务数据,智能化测试相比手动测试成本更低、效率更高、更准确的测试、使应用程序质量和性能更高,让我们一起开启智能化测试的新篇章。 体验链接 ➢ ➢ ➢ https://www.eolink.com/ ———————————————— 版权声明:本文为CSDN博主「哪 吒」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/guorui_java/article/details/128193662
-
泛型深入 泛型基本介绍 泛型的概述: 泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。 泛型的格式:<数据类型>; 注意:泛型只能支持引用数据类型。 集合体系的全部接口和实现类都是支持泛型的使用的。 泛型的好处: 统一数据类型。 把运行时期的问 题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。 泛型可以在很多地方进行定义: 定义在类后面 —> 泛型类 定义在方法上 —> 泛型方法 定义在接口后面 —> 泛型接口 如果需要存任意类型的数据, 我们可以不添加泛型或者规定泛型为Object(推荐使用泛型更加规范): public static void main(String[] args) { List<Object> list = new ArrayList<>(); list.add("aaa"); list.add(123); list.add(12.34); list.add(false); } 注意: 泛型必须是引用类型, 基础类型需要使用包装类 public static void main(String[] args) { // 例如int类型需要使用Integer包装类 List<Integer> lists = new ArrayList<>(); lists.add(123); } 自定义泛型类 泛型类的概述: 定义类时同时定义了泛型的类就是泛型类。 泛型类的格式:修饰符 class 类名<泛型变量> { } 例如:public class MyArrayList<T> { } 此处泛型变量 T 可以随便写为任意标识,常见的如E、T、K、V等。 作用:编译阶段可以指定数据类型,类似于集合的作用。 用下面的案例来举例: 模拟ArrayList集合自定义一个集合MyArrayList集合, 模拟添加和删除功能的泛型设计。 定义一个泛型类 public class MyArrayList<E> { public void add(E a) { System.out.println(a); } public void remove(E b) { System.out.println(b); } } 测试使用泛型类 public static void main(String[] args) { // 规定泛型 MyArrayList<String> list = new MyArrayList<>(); // 规定了泛型后, 只能使用规定的类型 list.add("aaa"); // aaa list.remove("bbb"); // bbb } 泛型类的原理: 把出现泛型变量(E、T、K、V)的地方全部替换成传输的真实数据类型, 做到约束数据类型的作用。 自定义泛型方法 泛型方法的概述: 定义方法时同时定义了泛型的方法就是泛型方法。 泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表) {} 例如: public <T> void show(T t) {} 作用:让方法中可以使用泛型接收一切实际类型的参数,使方法更具备通用性。 用下面一个例子进行讲解: 给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能! 定义一个接收任意类型数组的泛型方法 // 定义泛型方法 public static <T> void printArray(T[] arr) { StringBuilder stb = new StringBuilder("["); for (int i = 0; i < arr.length; i++) { stb.append(arr[i]).append(i == arr.length - 1 ? "": ", "); } stb.append("]"); System.out.println(stb); } 测试: 使用泛型方法 public static void main(String[] args) { Integer[] arr1 = {10, 20, 30}; printArray(arr1); // [10, 20, 30] String[] arr2 = {"aaa", "bbb", "ccc"}; printArray(arr2); // [aaa, bbb, ccc] } 泛型方法的原理: 把出现泛型变量的地方全部替换成传输的真实数据类型。 自定义泛型接口 泛型接口的概述: 使用了泛型定义的接口就是泛型接口。 泛型接口的格式:修饰符 interface 接口名称<泛型变量>{} 例如: public interface Data<E>{} 作用:泛型接口可以让实现类选择当前功能需要操作的数据类型 我们还是通过一个例子讲解: 需求: 教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作 由于在接口中方法的类型定义为学生类型和老师类型都是不合适的, 所以需要使用泛型, 创建一个泛型接口 public interface Date<E> { void add(E e); void delete(E e); void update(E e); } 再在创建实现类时, 传入真实的类型即可, 类似如下: class Teacher { } class TeacherData implements Date<Teacher> { @Override public void add(Teacher teacher) { } @Override public void delete(Teacher teacher) { } @Override public void update(Teacher teacher) { } } 泛型接口的原理: 实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。 泛型通配符和上下限 通配符: ? ? 可以在“使用泛型”的时候代表一切类型。 E T K V 是在定义泛型的时候使用的。 演示代码: // 表示接收任意类型的列表 public static void go(ArrayList<?> list) { } 泛型的上下限, 格式如下: ? extends Car: 表示 ? 必须是Car或者其子类 泛型上限 // 传入的类型必须是Car或者Car的子类 public static void go(ArrayList<? extends Car> list) { } 1 2 3 4 ? super Car : 表示 ? 必须是Car或者其父类 泛型下限(了解) // 传入的类型必须是Car或者Car的父类 public static void go(ArrayList<? super Car> list) { } ———————————————— 版权声明:本文为CSDN博主「学全栈的灌汤包」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_71485750/article/details/127379466
-
1. 案例1: Bean作用域的问题 现在有一个公共的 Bean,通过给 A 用户 和 B 用户使用, 然后在使用的过程中 A 偷偷的修改了公共 Bean 的数据, 导致 B 在使用时发生了预期之外的逻辑错误 (1) 公共 Bean: [名称是 java] @Component public class Users { @Bean public User user1() { User user = new User(); user.setId(1); user.setName("java"); return user; } } (2) A 用户使用时,进行了修改操作 [名称是 悟空, 进行了修改操作] @Controller public class BeanScopesController { @Autowired private User user1; public User getUser1() { User user = user1; System.out.println("Bean 原 name: " + user.getName()); user.setName("悟空"); return user; } } (3) B 用户再去使用公共 Bean 的时候 @Controller public class BeanScopesController2 { @Autowired private User user1; public User getUser1() { User user = user1; return user; } } (4) 打印 A 用户和 B 用户公共 Bean 的值 public class BeanScopesTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); BeanScopesController beanScopesController = context.getBean(BeanScopesController.class); System.out.println("A 对象修改之后 Name: " + beanScopesController.getUser1().toString()); BeanScopesController2 beanScopesController2 = context.getBean(BeanScopesController2.class); System.out.println("B 对象读取到是 Name: " + beanScopesController2.getUser1().toString()); } } (5) 运行程序 (6) 原因分析 存在这个问题的原因就是 Bean 默认情况下是 单例模式 (singleton), 也就是所有类使用的都是同一个对象, 使用同一个对象,那就是没有过多的生命周期的创建和销毁,从而可以提高性能,所以在 Spring 中 Bean 的作用域也是 单例模式的 2. 作用域 限定程序中变量的可用范围叫做作用域. Bean的作用域是指, Bean 在 Spring 整个框架中的某种行为,比如 单例模式的作用域, 意思就是 Bean 在整个 Spring 中只有一份, 它是全局共享的, 当有人修改了这个值后, 其他人读到的就是被修改的值 Spring 容器在初始化一个 Bean 的实例时,会同时指定该实例的作用域,Spring中有 6 种作用域 singleton: 单例模式 (默认,每次请求取的是同一个对象) prototype: 原型模式 (多例模式,每次请求创建一个新的对象) request: 请求作用域 (Spring MVC,每次请求创建一个对象) session: 会话作用域 (Spring MVC,在一个会话中共享一个对象) application: 全局作用域 (Spring MVC,所有人共用一个) websocket: HTTP WebSocket 作用域 (Spring WebSocket 项目中存在的一种) 3. 设置 Bean 的作用域 使用 @Scope 标签来声明 Bean 的作用域, @Scope 标签既可以修饰方法,也可以修饰类, @Scope 有两种设置方式 直接设置值: @Scope("prototype") 类似于枚举的常量方式: @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Component public class Users { @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Bean public User user1() { User user = new User(); user.setId(1); user.setName("java"); return user; } } 还是 案例1中的代码,只不过加上 Bean的作用域 原型模式后,运行程序,可以看到A修改后,B没有受到影响 4. Spring 的执行流程 Spring 执行流程: 启动 Spring 容器 实例化 Bean (分配内存空间) 将 Bean 注册到 Spring 中 (存操作) 将 Bean 装配到需要的类中 (取操作) 5. Bean 的生命周期 Bean 的生命周期: 实例化 Bean (给 Bean 分配内存空间) 买房子 设置属性 (Bean 对象注入) 装修 Bean 初始化 买家电 执行各种通知 (执行各种 Aware) 执行初始化的前置方法 执行构造方法,两种执行方式,一种是执行 @PostConstruct,另一种是执行 init-method 执行初始化的后置方法 4. 使用 Bean 入住 5. 销毁 Bean 卖房 @PreDestroy (销毁前方法) 重写 DisposableBean 接口方法 destroy-method public class BeanLifeComponent implements BeanNameAware { @PostConstruct public void postConstruct() { System.out.println("执行 PostConstruct()"); } public void init() { System.out.println("执行 init-method"); } public void use() { System.out.println("使用 bean"); } @PreDestroy public void preDestroy() { System.out.println("执行: preDestroy()"); } public void setBeanName(String s) { System.out.println("执行了 Aware 通知"); } } <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:content="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <content:component-scan base-package="com.beans"></content:component-scan> <beans> <bean id="beanLifeComponent" class="com.beans.BeanLifeComponent" init-method="init"></bean> </beans> </beans> public class BeanLifeTest { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config2.xml"); BeanLifeComponent lifeComponent = context.getBean(BeanLifeComponent.class); lifeComponent.use(); //执行销毁方法 context.destroy(); } } ———————————————— 版权声明:本文为CSDN博主「快到锅里来呀」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_58761900/article/details/128091598
-
一、Redis 实现好友关注 – 关注与取消关注需求:针对用户的操作,可以对用户进行关注和取消关注功能。在探店图文的详情页面中,可以关注发布笔记的作者具体实现思路:基于该表数据结构,实现2个接口关注和取关接口判断是否关注的接口关注是用户之间的关系,是博主与粉丝的关系,数据表如下:tb_followCREATE TABLE `tb_follow` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`user_id` bigint(20) unsigned NOT NULL COMMENT '用户id',`follow_user_id` bigint(20) unsigned NOT NULL COMMENT '关联的用户id',`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;id为自增长,简化开发核心代码FollowController//关注@PutMapping("/{id}/{isFollow}")public Result follow(@PathVariable("id") Long followUserId, @PathVariable("isFollow") Boolean isFollow) {return followService.follow(followUserId, isFollow);}//取消关注@GetMapping("/or/not/{id}")public Result isFollow(@PathVariable("id") Long followUserId) {return followService.isFollow(followUserId);}FollowService@Overridepublic Result follow(Long followUserId, Boolean isFollow) {// 1.获取登录用户Long userId = UserHolder.getUser().getId();String key = "follows:" + userId;// 1.判断到底是关注还是取关if (isFollow) {// 2.关注,新增数据Follow follow = new Follow();follow.setUserId(userId);follow.setFollowUserId(followUserId);boolean isSuccess = save(follow);if (isSuccess) {stringRedisTemplate.opsForSet().add(key, followUserId.toString());}} else {// 3.取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ?boolean isSuccess = remove(new QueryWrapper().eq("user_id", userId).eq("follow_user_id", followUserId));if (isSuccess) {// 把关注用户的id从Redis集合中移除stringRedisTemplate.opsForSet().remove(key, followUserId.toString());}}return Result.ok();}@Overridepublic Result isFollow(Long followUserId) {// 1.获取登录用户Long userId = UserHolder.getUser().getId();// 2.查询是否关注 select count(*) from tb_follow where user_id = ? and follow_user_id = ?Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();// 3.判断return Result.ok(count > 0);}代码编写完毕,进行测试代码测试点击进行关注用户关注成功取消关注测试成功二、Redis 实现好友关注 – 共同关注功能实现共同关注好友功能,首先,需要进入博主发布的指定笔记页,然后点击博主的头像去查看详细信息核心代码如下UserController// UserController 根据id查询用户@GetMapping("/{id}")public Result queryUserById(@PathVariable("id") Long userId){// 查询详情User user = userService.getById(userId);if (user == null) {return Result.ok();}UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);// 返回return Result.ok(userDTO);}BlogController@GetMapping("/of/user")public Result queryBlogByUserId(@RequestParam(value = "current", defaultValue = "1") Integer current,@RequestParam("id") Long id) {// 根据用户查询Page page = blogService.query().eq("user_id", id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));// 获取当前页数据List records = page.getRecords();return Result.ok(records);}那么如何实现共同好友关注功能呢?需求:利用Redis中的数据结构,实现共同关注功能。 在博主个人页面展示出当前用户与博主的共同关注思路分析: 使用Redis的Set集合实现,我们把两人关注的人分别放入到一个Set集合中,然后再通过API去查看两个Set集合中的交集数据改造核心代码:当用户关注某位用户后,需要将数据存入Redis集合中,方便后续进行共同关注的实现,同时取消关注时,需要删除Redis中的集合FlowServiceImpl@Overridepublic Result follow(Long followUserId, Boolean isFollow) {// 1.获取登录用户Long userId = UserHolder.getUser().getId();String key = "follows:" + userId;// 1.判断到底是关注还是取关if (isFollow) {// 2.关注,新增数据Follow follow = new Follow();follow.setUserId(userId);follow.setFollowUserId(followUserId);boolean isSuccess = save(follow);if (isSuccess) {stringRedisTemplate.opsForSet().add(key, followUserId.toString());}} else {// 3.取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ?boolean isSuccess = remove(new QueryWrapper().eq("user_id", userId).eq("follow_user_id", followUserId));if (isSuccess) {// 把关注用户的id从Redis集合中移除stringRedisTemplate.opsForSet().remove(key, followUserId.toString());}}return Result.ok();}// 具体获取好友共同关注代码@Overridepublic Result followCommons(Long id) {// 1.获取当前用户Long userId = UserHolder.getUser().getId();String key = "follows:" + userId;// 2.求交集String key2 = "follows:" + id;Set intersect = stringRedisTemplate.opsForSet().intersect(key, key2);if (intersect == null || intersect.isEmpty()) {// 无交集return Result.ok(Collections.emptyList());}// 3.解析id集合List ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());// 4.查询用户List users = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());return Result.ok(users);}进行测试⛵小结以上就是【Bug 终结者】对 微服务Spring Boot 整合 Redis 实现 好友关注 的简单介绍,Redis 实现好友关注功能也是 利用Set集合、ZSet集合实现这样一个需求,同时,采用Redis来实现更加的快速,减少系统的消耗,更加快速的实现数据展示! 下篇博文我们继续 关注 如何 使用Redis 实现推送消息到粉丝收件箱以及滚动分页查询!————————————————版权声明:本文为CSDN博主「Bug 终结者」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_45526437/article/details/128276229
上滑加载中
推荐直播
-
华为云码道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华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签