• 关于

    重载覆盖

    的搜索结果

回答

override(重写,覆盖) 1、方法名、参数、返回值相同。 2、子类方法不能缩小父类方法的访问权限。 3、子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。 4、存在于父类和子类之间。 5、方法被定义为final不能被重写。 overload(重载,过载) 1、参数类型、个数、顺序至少有一个不相同。 2、不能重载只有返回值不同的方法名。 3、存在于父类和子类、同类中。 方法的重写(Overriding)和重载(Overloading)是Java多态性的不同表现。 重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个类中多态性的一种表现。 如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了. 如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型或有不同的参数次序,则称为方法的重载(Overloading)。不能通过访问权限、返回类型、抛出的异常进行重载.Override 特点 1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;2、覆盖的方法的返回值必须和被覆盖的方法的返回一致; 3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类; 4、被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。 2.Overload 特点 1、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int, float), 但是不能为fun(int, int)); 2、不能通过访问权限、返回类型、抛出的异常进行重载; 3、方法的异常类型和数目不会对重载造成影响; 4、对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。
小志7980 2019-12-02 01:48:36 0 浏览量 回答数 0

回答

java中的方法重载发生在同一个类里面两个或者多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。 覆盖者可能不会限制它所覆盖的方法的访问。 重载(Overloading) (1)方法重载是让类以统一的方法处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数(类型)。重载Override是一个类中多态性的一种表现。 (2)java的方法重载,就是在类中可以创建多个方法,他们具有相同的名字,但具有不同参数和不同的定义。调用方法时通过传递给他们不同的参数个数和参数类型来决定具体使用那个方法,这就是多态性。 (3)重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不同。无法以返回类型来作为重载函数的区分标准。
小勿悔 2019-12-02 01:03:31 0 浏览量 回答数 0

回答

1、重载:一个类中可以有多个相同方法名的,但是参数类型和个数都不一样。这是重载。 2、重写:子类继承父类,则子类可以通过实现父类中的方法,从而新的方法把父类旧的方法覆盖。
剑曼红尘 2020-04-10 12:55:38 0 浏览量 回答数 0

回答

1.帮助自己检查是否正确的复写了父类中已有的方法2.告诉读代码的人,这是一个复写的方法关于第一点,我给你再说明一下:假设你现在要覆盖一个方法,然后因为你粗心,写错了参数的类型,刚好你又没有写@override注解,那么编辑器(eclipse)就不会提示你。这时候就产生了一个隐藏的bug,明明是准备复写方法,结果却是重载了方法。// 父类的方法: public void test(int i) { System.out.println(i); } // 子类重写 // 第一种写法,正确 @Override public void test(int i) { System.out.println(i + 1); } // 注意:这里因为人为粗心导致 i 的类型写成 float,没有写@Override注解,编辑器不会报错,那这里方法覆盖就变成了方法重载。 public void test(float i) { System.out.println(i + 1); }
蛮大人123 2019-12-02 02:12:26 0 浏览量 回答数 0

问题

C与C++的区别

C是面向过程,C++是面向对象。 C++里有函数重载,C中没有函数重载。 C++是面向对象的,有重载 继承 多态三种特性 然后面试官问我多态的用处,坦率的讲我所理解的多态如下: class animal { virtual vo...
a123456678 2019-12-01 19:51:01 948 浏览量 回答数 1

回答

1.this表示的是指向一个实例吗。如果不是指向一个实例,那么为什么this可以参与instanceof运算或当作引用参数传进方法里,super为什么不能?-- this表示的是指向一个实例。 System.out.println(s instanceof SubT3);//false 这个地方之所以返回false 是因为 s的类型是 SuperT3 所以 “s instanceof SubT3” 是返回false2.如果this表示当前对象,那么我在第4行new的SubT3的实例,在第14行为什么没有打印出peter呢。--首先说this的类型是SubT3 这是确定的,所以this instanceof SuperT3 和 this instanceof SubT3都是true. 第14行打印出Jack是正确的。要从内存结构看,子类是无法覆盖父类的成员变量的,所以对于SubT3 来说里面有两个 name, 一个是Jack 一个是peter。那么究竟是调用哪个name呢?是调用基类的name,因为成员变量是不能重载的,也就是说 调用成员变量的函数在基类中 则使用基类的成员变量 调用成员变量的函数在子类中则使用子类中的成员变量。 如果想要 输出peter,只需要在SubT3 重载func 也就是将func的代码复制在SubT3中 就会输出peter. 记住:成员变量是不会被重载的 只有 函数会被重载。3.如果在第14行时,this只表示SuperT3的引用(我也不知道到底引用什么),那为什么第16行没有打印出与第6行相同的结果呢?--看第2条的回答 this不是superT3 是 subT3 只是因为成员变量是无法重载的 依赖于调用它的函数所在类。另外说明一下,如果基类的成员变量可以被同名的子类中成员变量替换,那会产生灾难的后果。比如基类中有个数组 里面存储了一些 标识 比如 0 1 2 而在子类中 声明了同样一个名字的数组 里面是 3 4 5 那么基类运行到基类的方法的时候 本来处理 0 1 2 现在却处理 3 4 5 会产生不可预知的结果。如果想改变基类的行为 重载他的函数 重新定义新的行为。
蛮大人123 2019-12-02 01:49:27 0 浏览量 回答数 0

回答

重载(Overloading)和重写(Overriding)是Java中两个比较重要的概念。但是对于新手来说也比较容易混淆。本文通过两个简单的例子说明了他们之间的区别。 定义 重载 简单说,就是函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。 重写 重写指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法。 重载 VS 重写 关于重载和重写,你应该知道以下几点: 1、重载是一个编译期概念、重写是一个运行期间概念。 2、重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法。 3、重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法 4、因为在编译期已经确定调用哪个方法,所以重载并不是多态。而重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关。(注:严格来说,重载是编译时多态,即静态多态。但是,Java中提到的多态,在不特别说明的情况下都指动态多态) 重写的例子 下面是一个重写的例子,看完代码之后不妨猜测一下输出结果: class Dog{ public void bark(){ System.out.println("woof "); } } class Hound extends Dog{ public void sniff(){ System.out.println("sniff "); } public void bark(){ System.out.println("bowl"); } } public class OverridingTest{ public static void main(String [] args){ Dog dog = new Hound(); dog.bark(); } } 输出结果: bowl 上面的例子中,dog对象被定义为Dog类型。在编译期,编译器会检查Dog类中是否有可访问的bark()方法,只要其中包含bark()方法,那么就可以编译通过。在运行期,Hound对象被new出来,并赋值给dog变量,这时,JVM是明确的知道dog变量指向的其实是Hound对象的引用。所以,当dog调用bark()方法的时候,就会调用Hound类中定义的bark()方法。这就是所谓的动态多态性。 重写的条件 参数列表必须完全与被重写方法的相同; 返回类型必须完全与被重写方法的返回类型相同; 访问级别的限制性一定不能比被重写方法的强; 访问级别的限制性可以比被重写方法的弱; 重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常 重写的方法能够抛出更少或更有限的异常(也就是说,被重写的方法声明了异常,但重写的方法可以什么也不声明) 不能重写被标示为final的方法; 如果不能继承一个方法,则不能重写这个方法。 重载的例子 class Dog{ public void bark(){ System.out.println("woof "); } //overloading method public void bark(int num){ for(int i=0; i<num; i++) System.out.println("woof "); } } 上面的代码中,定义了两个bark方法,一个是没有参数的bark方法,另外一个是包含一个int类型参数的bark方法。在编译期,编译期可以根据方法签名(方法名和参数情况)情况确定哪个方法被调用。 重载的条件 被重载的方法必须改变参数列表; 被重载的方法可以改变返回类型; 被重载的方法可以改变访问修饰符; 被重载的方法可以声明新的或更广的检查异常; 方法能够在同一个类中或者在一个子类中被重载。 参考资料 Overriding vs. Overloading in Java
montos 2020-06-01 15:49:27 0 浏览量 回答数 0

问题

tomcat能否实时重载servlet?

最近在学servlet,但是有一件事情一直很头疼,每次修改完一个类,然后就直接覆盖到tomcat下面的目录里,可是打开浏览器显示的还是刚刚修改前的结果。我想,是不是tomcat 已经在服务器端自己缓存原来那个类啊?可是老是重启服务器又觉得好...
落地花开啦 2019-12-01 19:24:06 1123 浏览量 回答数 1

回答

1 . 静态分派: 所有依赖静态类型来定位方法执行版本的分派动作,都称为静态分派,静态分派的最典型应用就是多态性中的方法重载。静态分派发生在编译阶段,因此确定静态分配的动作实际上不是由虚拟机来执行的。动态分派     动态分派与多态性的另一个重要体现——方法覆写有着很紧密的关系。向上转型后调用子类覆写的方法便是一个很好地说明动态分派的例子。这种情况很常见,因此这里不再用示例程序进行分析。很显然,在判断执行父类中的方法还是子类中覆盖的方法时,如果用静态类型来判断,那么无论怎么进行向上转型,都只会调用父类中的方法,但实际情况是,根据对父类实例化的子类的不同,调用的是不同子类中覆写的方法,很明显,这里是要根据变量的实际类型来分派方法的执行版本的。而实际类型的确定需要在程序运行时才能确定下来,这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
hiekay 2019-12-02 01:42:12 0 浏览量 回答数 0

回答

十分感谢@jFinal大大不厌其烦的回答 问题终解决,是由于,isTakeOverDbPaginate,isTakeOverModelPaginate没有重载造成的,并且要返回false,现把MyAnsiDialect类代码再完整贴出来: packagecom.demo.common;importjava.sql.Connection;importjava.sql.SQLException;importcom.jfinal.plugin.activerecord.Page;importcom.jfinal.plugin.activerecord.Record;importcom.jfinal.plugin.activerecord.dialect.AnsiSqlDialect;publicclassMyAnsiDialectextendsAnsiSqlDialect{@OverridepublicvoidforPaginate(StringBuildersql,intpageNumber,intpageSize,Stringselect,StringsqlExceptSelect){System.out.println("-------MyAnsiDialect.forPaginate--------------------------------");intnotInPageNum=(pageNumber-1)*pageSize;sql.append("selecttop").append(pageSize).append("").append(select);sql.append(sqlExceptSelect).append("whereidnotin(");sql.append("selecttop").append(notInPageNum).append("id");sql.append(sqlExceptSelect).append("orderbyiddesc");sql.append(")orderbyiddesc");System.out.println("--------拼接后的分页语句---------------------------");System.out.println(sql.toString());}@OverridepublicbooleanisTakeOverDbPaginate(){returnfalse;}@OverridepublicbooleanisTakeOverModelPaginate(){returnfalse;}}  如果isTakeOverDbPaginateisTakeOverModelPaginate返回true的话,你可以去覆盖takeOverDbPaginate与takeOverModelPaginate方法来实现分页,只不过要多写点代码,这两个takeOver中需要实现查询总记录数,以及计算总页数的逻辑,具体可以参考AnsiSqlDialect中的takeOver方法  最后一个String型的参数: "t1.id"是干什么的?前面的sql中没有带问号,所以这个参数放这里肯定是错误的这个是一个自增量字段,用来orderby的 我把这个t1.id去掉可以正常运行了,但好像没有运行我自定义的分页方法,输出的SQL语句如下,查的是所有记录? select*from(selectt1.id,t1.dh,t1.dDate,t1.vTec_Dh,t1.nMK_id,t1.vCustNo,t1.vContatPer,t1.vContaTEL,t1.vCurrencyNo,t1.phr,t1.phr_name,t1.lphbz,t1.phrq,t2.StyleCodeasPadNo,t2.size_fwasSizeRangefromSal_Quotationt1leftjoinMould_Stylet2ont1.nMK_id=t2.idleftjoincustoms_matKindt3ont2.vCustoms_code=t3.kindCodeleftjoinpay_mstrt4ont1.vPayTerm=t4.pay_codeleftjoinSCR_Transport_Mstrt5ont1.vDelyTerm=t5.transCodeleftjoinBase_customs_typet6ont1.vCustomsFormat=t6.vCode)t1 怎么回事? 确定查的是所有记录,并没有走自定义类MyAnsiDialect中的代码,也就是说并没有生成拼接的分页SQL语句.在配置类configPlugin中,我不是已经设置启用这个方言了吗?为什么没走这段代码?是哪里配置有问题吗?StringkeyId这个参数必须去掉   因为你并没有覆盖掉AnsiSqlDialect中的paginate方法,所以你自己的方法没有被调用,方法定义如下: publicvoidforPaginate(StringBuildersql,intpageNumber,intpageSize,Stringselect,StringsqlExceptSelect)  StringkeyId这个参数必须去掉,否则不算方法覆盖,无法多态 Model.class中的paginate方法如下: privatePage<M>paginate(Configconfig,Connectionconn,intpageNumber,intpageSize,Stringselect,StringsqlExceptSelect,Object...paras){......//这句没有正确运行自定义方言?config.dialect.forPaginate(sql,pageNumber,pageSize,select,sqlExceptSelect);List<M>list=find(conn,sql.toString(),paras);returnnewPage<M>(list,pageNumber,pageSize,totalPage,(int)totalRow);} 查看源码发现,这个方法是分页的必经之路呀,怎么没走作用? config.dialect.forPaginate(sql,pageNumber,pageSize,select,sqlExceptSelect);  你的实现多了个参数,StringkeyId,必须不会认 引用来自“JFinal”的评论   因为你并没有覆盖掉AnsiSqlDialect中的paginate方法,所以你自己的方法没有被调用,方法定义如下: publicvoidforPaginate(StringBuildersql,intpageNumber,intpageSize,Stringselect,StringsqlExceptSelect)  StringkeyId这个参数必须去掉,否则不算方法覆盖,无法多态也可以只是个问号占位,然后keyId放在后面的Object...paras列表之中直接放sqlExceptSelect这个参数之中啊 去掉这个keyId还是不行,还是没走这段代码: publicclassMyAnsiDialectextendsAnsiSqlDialect{@OverridepublicvoidforPaginate(StringBuildersql,intpageNumber,intpageSize,Stringselect,StringsqlExceptSelect){System.out.println("-------MyAnsiDialect.forPaginate--------------------------------");intnotInPageNum=(pageNumber-1)*pageSize;sql.append("selecttop").append(pageSize).append("");sql.append(sqlExceptSelect).append("whereidnotin(");sql.append("selecttop").append(notInPageNum).append(sqlExceptSelect).append("orderbyid");sql.append(")torderbyt1.id");System.out.println("---------------拼接后的分页语句---------------------------");System.out.println(sql);}}   引用来自“andying”的评论 去掉这个keyId还是不行,还是没走这段代码: publicclassMyAnsiDialectextendsAnsiSqlDialect{@OverridepublicvoidforPaginate(StringBuildersql,intpageNumber,intpageSize,Stringselect,StringsqlExceptSelect){System.out.println("-------MyAnsiDialect.forPaginate--------------------------------");intnotInPageNum=(pageNumber-1)*pageSize;sql.append("selecttop").append(pageSize).append("");sql.append(sqlExceptSelect).append("whereidnotin(");sql.append("selecttop").append(notInPageNum).append(sqlExceptSelect).append("orderbyid");sql.append(")torderbyt1.id");System.out.println("---------------拼接后的分页语句---------------------------");System.out.println(sql);}}  代码没生效,重新编译一下,启动下服务,eclipse有时候会抽风
爱吃鱼的程序员 2020-06-14 22:33:30 0 浏览量 回答数 0

回答

面向对象的特征主要有以下几个方面: 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。 封装 封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。 继承 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。 关于继承如下 3 点请记住: 子类拥有父类非 private 的属性和方法。 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 子类可以用自己的方式实现父类的方法。(以后介绍)。 多态 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。 在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。 其中Java 面向对象编程三大特性:封装 继承 多态 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。 继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承可以提高代码复用性。继承是多态的前提。 关于继承如下 3 点请记住: 子类拥有父类非 private 的属性和方法。 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 子类可以用自己的方式实现父类的方法。 多态性:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。 在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。 方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。 一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事: 方法重写(子类继承父类并重写父类中已有的或抽象的方法); 对象造型(用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
问问小秘 2020-03-27 17:36:33 0 浏览量 回答数 0

问题

【精品问答】Java实战200例(附源码)

Java实战200例(附源码) 1.编写一个Java程序,用if-else语句判断某年份是否为闰年 2. 编写一个Java程序在屏幕上输出1!+2!+...
珍宝珠 2020-02-14 11:55:46 16104 浏览量 回答数 10

回答

你是想要静态资源的权限控制? @Anur回复 @Anur:shiro是啊... springboot和spring毕竟不一样,SpringBoot自己配置了很多信息比如: 自动配置的静态资源:在自动配置类的addResourceHandlers方法中定义了一下静态资源的自动配置     ①类路径文件:把类路径下的/static、/public、/resources和/META-INF/resources文件夹下的静态文件直接映射为/**,可以通过http://localhost:8080/**来访问。     ②webjar:webjar就是将我们常用的脚本框架封装在jar包中的jar包。把webjar的/META-INF/resources/webjars/下的静态资源文件映射为/webjars/**,可以通过http://localhost:8080/webjars/**来访问。     ③自动配置的Formatter和Converter。我们可以看一下WebMvcAutoConfiguration类中的定义addFormatters(FormatterRegistry)方法。从代码中可以看出,只要我们定义了Converter、GenericConverter和Formatter接口的实现类的Bean,这些Bean就会自动注册到SpringMVC中。     ④自动配置的HttpMessageConverter:在WebMvcAutoConfiguration中,我们注册了messageConverters,在configureMessageConverters方法,在这里直接注入了HttpMessageConverter的Bean,而这个Bean是在HttpMessageConverterAutoConfiguration类中定义的,我们自动注册的HttpMessageConverter除了SpringMVC默认的ByteArrayMessageConverter、StringHttpMessageConverter、SourceHttpMessageConverter、ResourceHttpMessageConverter、 AllEncompassingFormHttpMessageConverter外,在我们的HttpMessageConverterAutoConfiguration的自动配置文件里还引入了JacksonHttpMessageConverter Configuration和GsonHttpMessageConverter Configuration,使得我们获得了额外的HttpMessageConverter。         若jackson的jar包在类路径上,则SpringBoot通过JacksonHttpMessageConverters Configuration增加MappingJackson2HttpMessage Converter和MappingJackson2XmlHttpMessage Converter。         若gason的jar包在类路径上,则SpringBoot通过GsonHttpMessageConverter Configuration增加GsonHttpMessage。     ⑤静态首页的支持:把静态index.html文件放置在如下目录:         classpath:META-INF/resources/index.html         classpath:/resources/index.html         classpath:/static/index.html         classpath:/publik/index.html 当我们访问应用根目录http://localhost:8080/时,会直接映射。     自然了如果你想自己增加也是可以的,类似下面的写法就行,自己再改改吧,SpringBoot其实默认有上面的那些了 程序的静态文件(js、css、图片)等需要直接访问,这时我们需要在配置里重写addResourceHandlers方法来实现。@Configuration@EnableWebMvc//1会开启一些默认配置,比如ViewResolver或者MessageConverter等,开启SpringMVC支持,没有这个注释,重写WebMvcConfigurerAdapter无效@EnableScheduling//开始计划任务的支持@ComponentScan("com.wisely.highlight_springmvc4")publicclassMyMvcConfigextendsWebMvcConfigurerAdapter{//2重写其方法可以对SpringMVC进行配置   @Bean   publicInternalResourceViewResolverviewResolver(){      InternalResourceViewResolverviewResolver=newInternalResourceViewResolver();      viewResolver.setPrefix("/WEB-INF/classes/views/");      viewResolver.setSuffix(".jsp");      viewResolver.setViewClass(JstlView.class);      returnviewResolver;   }   /**   *程序的静态文件需要直接访问,重写该方法即可   */   @Override   publicvoidaddResourceHandlers(ResourceHandlerRegistryregistry){      //addResourceLocations指的是文件放置的目录,addResourceHandler指的是对外暴露的访问路径      registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");//3   }   @Bean   //1配置拦截器的Bean   publicDemoInterceptordemoInterceptor(){      returnnewDemoInterceptor();   }   @Override   publicvoidaddInterceptors(InterceptorRegistryregistry){//2重写addInterceptors方法,注册拦截器      registry.addInterceptor(demoInterceptor());   }   /**   *快捷的ViewController,适用于无任何业务处理,只是简单的页面跳转   */   @Override   publicvoidaddViewControllers(ViewControllerRegistryregistry){      registry.addViewController("/index").setViewName("/index");//快捷的ViewController      registry.addViewController("/toUpload").setViewName("/upload");      registry.addViewController("/converter").setViewName("/converter");      registry.addViewController("/sse").setViewName("/sse");      registry.addViewController("/async").setViewName("/async");   }   /**   *路径匹配参数设置   *在SpringMVC中,路径参数如果带"."的话,"."后面的值将会忽略,重写此方法可以不忽略"."后面的参数   */   @Override   publicvoidconfigurePathMatch(PathMatchConfigurerconfigurer){      configurer.setUseSuffixPatternMatch(false);   }   /**   *文件上传的配置   */   @Bean   publicMultipartResolvermultipartResolver(){      CommonsMultipartResolvermultipartResolver=newCommonsMultipartResolver();      multipartResolver.setMaxUploadSize(1000000);      returnmultipartResolver;   }   /**   *配置自定义的HttpMessageConverter的Bean,在SpringMVC里注册HttpMessageConverter有两个方法   *①configureMessageConverters:重载会覆盖掉SpringMVC默认注册的多个HttpMessageConverter   *②extendMessageConverters:仅添加一个自定义的HttpMessageConverter,不覆盖默认注册的HttpMessageConverter   */   @Override   publicvoidextendMessageConverters(List<HttpMessageConverter<?>>converters){      converters.add(converter());   }   @Bean   publicMyMessageConverterconverter(){      returnnewMyMessageConverter();   }}   继承,重写方法我感觉可行
爱吃鱼的程序员 2020-06-07 22:43:08 0 浏览量 回答数 0

问题

苦逼在阿里云的主机升级成功,分享使用云主机搭建高性能博客的经验

系统配置(升级前):1M带宽,512MB内存,60G磁盘。 使用伺服套件:Tengine  Apache PHP MySQL 演示ÿ...
enj0y 2019-12-01 20:20:26 24079 浏览量 回答数 13

问题

【精品问答】Java基础测试题 2 答案 | 看你会多少

一、填空题 1、Java中通过extends关键字实现继承。 2、一个类只能继承一个父类,但能实现多个接口。 3、当子类中定义的方法与父类方法同名且参数类型及个数、返回值类型相同时,称子类方法覆写父类方法&...
游客pklijor6gytpx 2019-12-01 22:04:54 334 浏览量 回答数 0

回答

创建一个包含模板间共享布局的模板,通常这样的模板包含: 页面头部、导航栏、脚部、内容展示区域。 Layout.html <!DOCTYPE html> <html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> <head> <title>Layout page</title> <script src="common-script.js"></script> </head> <body> <header> <h1>My website</h1> </header> <section layout:fragment="content"> <p>Page content goes here</p> </section> <footer> <p>My footer</p> <p layout:fragment="custom-footer">Custom footer here</p> </footer> </body> </html> 注意事项: 1. html标签上附上命名空间 2. section与脚部p标签上使用layout:fragment属性 这些是布局中的插入候选槽点,通过匹配内容模板中片段进行替换。 创建一些内容模板: Content1.html <!DOCTYPE html> <html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{Layout}"> <head> <title>Content page 1</title> <script src="content-script.js"></script> </head> <body> <section layout:fragment="content"> <p>This is a paragraph from content page 1</p> </section> <footer> <p layout:fragment="custom-footer">This is some footer content from content page 1</p> </footer> </body> </html> html标签中的layout:decorate说明哪一个布局模板使用这个内容模板进行装饰。内容模板定义自身标题与脚本、content与custom-footer片段。custom-footer片段处于footer元素内部,这其实是不必要的,但是可能会是很方便的,如果想要做内容模板的静态模板,这是一开始使用Thymeleaf的原因之一。 在一个模板内片段名称必须唯一,否则可能会出现片段不匹配,各种各样的可笑事情会接踵而至。 不管如何,一旦告知Thymeleaf处理Content1.html,最终的页面会是这样子: <!DOCTYPE html> <html> <head> <title>Content page 1</title> <script src="common-script.js"></script> <script src="content-script.js"></script> </head> <body> <header> <h1>My website</h1> </header> <section> <p>This is a paragraph from content page 1</p> </section> <footer> <p>My footer</p> <p>This is some footer content from content page 1</p> </footer> </body> </html> 内容模板装饰Layout.html,结果是布局的组合,加上内容模板的片段(两个模板的<head>元素,来自内容模板的<title>元素替换布局文件内的,所有的元素来自布局文件,但是由所有指定的内容模板进行替换) 想了解更多可以如何控制<head>元素合并,参看<head>元素合并一小节。 装饰进程重定向处理从内容模板至布局,将layout:fragment部分从内容模板中挑选出来,因为布局需要它们。正因如此,任何在layout:fragment之外的东西实际从未得到执行,这说明在内容模板中不能这样做: <div th:if="${user.admin}"> <div layout:fragment="content"> ... </div> </div> 如果布局模板想要’内容’片段,那么会得到那个片段,不顾任何所在条件,因为那些条件不会执行。 如果说只想用绝对最小HTML代码量替换装饰器脚部: Content2.html <p layout:decorate="~{Layout}" layout:fragment="custom-footer"> This is some footer text from content page 2. </p> 这就是全部所需的东西!<p>标签同时用作根元素与片段定义,生成一个像这样的页面: <!DOCTYPE html> <html> <head> <title>Layout page</title> <script src="common-script.js"></script> </head> <body> <header> <h1>My website</h1> </header> <section> <p>Page content goes here</p> </section> <footer> <p>My footer</p> <p> This is some footer text from content page 2. </p> </footer> </body> </html> 可以把布局看作母版,会得以填充或者被内容(子模板)覆盖,仅当内容会填充/覆盖父类。以这种方式,布局充当某种’默认’,内容充当这种默认之上的实现。 给布局传送数据 子模板向上给母版布局传送数据,在涉及到布局/装饰过程的任意元素上使用th:with/ data-th-with属性处理器,可以在任何地方layout:decorate/ data-layout-decorate 或者可以发现 layout:fragment/data-layout-fragment, 例如: 孩子/内容模板: <html layout:decorate="your-layout.html" th:with="greeting='Hello!'"> 1 父类/布局模板: <html> ... <p th:text="${greeting}"></p> <!-- You'll end up with "Hello!" in here --> 将来,或许会增加支持使用分片局部变量,很像Thymeleaf用于创建片段签名。 配置标题 鉴于布局方言自动用内容模板中所发现的重载布局<title>,可能会发现自己重复布局中发现的标题部分,尤其是想要创建面包屑或者在页面标题中保留页面名称。layout:title-pattern处理器可以免除重复布局标题的问题,通过使用一些特殊标记以想要标题如何出现的模式。 这是一个例子: Layout.html <!DOCTYPE html> <html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> <head> <title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">My website</title> </head> ... </html> layout:title-pattern处理器采取简单字符串,识别两种特殊标记:$LAYOUT_TITLE与$CONTENT_TITLE。每种标记在结果页中会被各自相应的标题替换。所以,如果有下面的内容模板: Content.html <!DOCTYPE html> <html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="Layout"> <head> <title>My blog</title> </head> ... </html> 结果页会是这样: <!DOCTYPE html> <html> <head> <title>My website - My blog</title> </head> ... </html> 这对<title>元素内的静态/内联文本或者<title>元素上发现使用th:text的动态文本均有效。 上述例子中的模式在布局中指定,所以对所有使用布局的内容模板均适用。如果在内容模板中指定另一种标题模式,那么会覆盖布局中发现的那个,允许细粒度的控制标题的展现形式。 可重用模板 假如发现有一些HTML或者结构经常性地重复,想要做成自己的模板从不同地方插入以便减少代码重复。(模块化Thymeleaf?)这个的例子可能会是一个模态面板,由几个HTML元素与CSS类构成,在网页应用中产生一个新窗口的效果: Modal.html <!DOCTYPE html> <html> <body> <div id="modal-container" class="modal-container" style="display:none;"> <section id="modal" class="modal"> <header> <h1>My Modal</h1> <div id="close-modal" class="modal-close"> <a href="#close">Close</a> </div> </header> <div id="modal-content" class="modal-content"> <p>My modal content</p> </div> </section> </div> </body> </html> 会发现可以将一些东西转换成像头部、ID的变量,以便包含Modal.html的页面可以设定它们自己的名称/ID。继续尽可能泛型化编写模态代码,然而会遇到填充自己的模态框内容的问题,那是开始接触一些限制的地方。 一些页面使用单一消息的模态框,其他想要使用模态框容纳一些更复杂的东西比如接受用户输入的表单。模态框可能性变得无休无止,但是未支持想象,发现自己得不得将这段模态框代码拷贝到每一个模板中,每一次使用场合变化相应内容,重复同样的HTML代码维持同样的外观感受,打破了过程中的DRY原则。 主要妨碍适当重用的事情是无法将HTML元素传递至插入模板中。这正是layout:insert有用的地方。它运作起来完全像th:insert,但是通过指定与实现片段很像内容/布局实例,可以创建一个公共的结构,对插入它的模板使用场合作出响应。 这是一个更新的模态框模板,使用Thymeleaf与layout:fragment属性定义一个可替换的模态框内容部分以变得更加泛型化: Modal2.html Modal2.html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> <body layout:fragment="modal(modalId, modalHeader)"> <div th:id="${modalId} + '-container'" class="modal-container" style="display:none;"> <section th:id="${modalId}" class="modal"> <header> <h1 th:text="${modalHeader}">My Modal</h1> <div th:id="'close-' + ${modalId}" class="modal-close"> <a href="#close">Close</a> </div> </header> <div th:id="${modalId} + '-content'" class="modal-content"> <div layout:fragment="modal-content"> <p>My modal content</p> </div> </div> </section> </div> </body> </html> 现在可以插入这个模板,使用layout:insert处理器与无论怎样需要实现modal-content片段,通过在调用模板插入元素内创建同样名称的片段: Content.html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> ... <div layout:insert="Modal2 :: modal(modalId='message', modalHeader='Message')" th:remove="tag"> <p layout:fragment="modal-content">Message goes here!</p> </div> ... </html> 就像内容/布局实例,插入模板layout:fragment会被匹配片段名称的元素替换掉。在这种场合下,Modal2.html的整个modal-content部分会被上述自定义段落替换掉。这是结果: <!DOCTYPE html> <html> ... <div id="message-container" class="modal-container" style="display:none;"> <section id="message" class="modal"> <header> <h1>Message</h1> <div id="close-message" class="modal-close"> <a href="#close">Close</a> </div> </header> <div id="message-content" class="modal-content"> <p>Message goes here!</p> </div> </section> </div> ... </html> 定义在模板内包含Modal2.html的自定义消息作为模态框内容的一部分。在插入模板上下文环境中的片段与用于内容/布局过程中的片段一样工作:如果片段未在模板中定义,那么它不会覆盖插入模板中的内容,使得在可重用版本中创建默认。
景凌凯 2020-04-29 21:11:25 0 浏览量 回答数 0

问题

应该返回false的用户输入返回true

到目前为止,我一直在程序中使用运算符比较所有字符串。但是,我遇到了一个错误,将其中一个更改为错误.equals(),并修复了该错误。 true==falseÿ...
养狐狸的猫 2019-12-01 20:00:45 8 浏览量 回答数 0

问题

Java技术1000问(3)【精品问答】

为了方便Java开发者快速找到相关技术问题和答案,开发者社区策划了Java技术1000问内容,包含最基础的Java语言概述、数据类型和运算符、面向对象等维度内容。 我们会以每天至少50条的速度,增...
问问小秘 2020-06-02 14:27:10 11463 浏览量 回答数 3

回答

遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么? 遍历方式有以下几种: for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。 foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。 最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。 如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。如果没有实现该接口,表示不支持 Random Access,如LinkedList。 推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。 说一下 ArrayList 的优缺点 ArrayList的优点如下: ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。ArrayList 在顺序添加一个元素的时候非常方便。 ArrayList 的缺点如下: 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。插入元素的时候,也需要做一次元素复制操作,缺点同上。 ArrayList 比较适合顺序添加、随机访问的场景。 如何实现数组和 List 之间的转换? 数组转 List:使用 Arrays. asList(array) 进行转换。List 转数组:使用 List 自带的 toArray() 方法。 代码示例: ArrayList 和 LinkedList 的区别是什么? 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。 补充:数据结构基础之双向链表 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。 ArrayList 和 Vector 的区别是什么? 这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。性能:ArrayList 在性能方面要优于 Vector。扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。 Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。 Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。 插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性? ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。 Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较ArrayList差。 LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快。 多线程场景下如何使用 ArrayList? ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样: 为什么 ArrayList 的 elementData 加上 transient 修饰? ArrayList 中的数组定义如下: private transient Object[] elementData; 再看一下 ArrayList 的定义: public class ArrayList extends AbstractList implements List<E>, RandomAccess, Cloneable, java.io.Serializable 可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现: 每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。 List 和 Set 的区别 List , Set 都是继承自Collection 接口 List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。 Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。 另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。 Set和List对比 Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。 List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变 Set接口 说一下 HashSet 的实现原理? HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。 HashSet如何检查重复?HashSet是如何保证数据不可重复的? 向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。 HashSet 中的add ()方法会使用HashMap 的put()方法。 HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。 以下是HashSet 部分源码: hashCode()与equals()的相关规定: 如果两个对象相等,则hashcode一定也是相同的 两个对象相等,对两个equals方法返回true 两个对象有相同的hashcode值,它们也不一定是相等的 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖 hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。 ** ==与equals的区别** ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同 ==是指对内存地址进行比较 equals()是对字符串的内容进行比较3.==指引用是否相同 equals()指的是值是否相同 HashSet与HashMap的区别 Queue BlockingQueue是什么? Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。 在 Queue 中 poll()和 remove()有什么区别? 相同点:都是返回第一个元素,并在队列中删除返回的对象。 不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。 代码示例: Queue queue = new LinkedList (); queue. offer("string"); // add System. out. println(queue. poll()); System. out. println(queue. remove()); System. out. println(queue. size()); Map接口 说一下 HashMap 的实现原理? HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。 HashMap 基于 Hash 算法实现的 当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。 需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn) HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现 在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。 JDK1.8之前 JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 JDK1.8之后 相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 JDK1.7 VS JDK1.8 比较 JDK1.8主要解决或优化了一下问题: resize 扩容优化引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。 HashMap的put方法的具体流程? 当我们put的时候,首先计算 key的hash值,这里调用了 hash方法,hash方法实际是让key.hashCode()与key.hashCode()>>>16进行异或操作,高16bit补0,一个数和0异或不变,所以 hash 函数大概的作用就是:高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞。按照函数注释,因为bucket数组大小是2的幂,计算下标index = (table.length - 1) & hash,如果不做 hash 处理,相当于散列生效的只有几个低 bit 位,为了减少散列的碰撞,设计者综合考虑了速度、作用、质量之后,使用高16bit和低16bit异或来简单处理减少碰撞,而且JDK8中用了复杂度 O(logn)的树结构来提升碰撞下的性能。 putVal方法执行流程图 ①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容; ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③; ③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals; ④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤; ⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可; ⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。 HashMap的扩容操作是怎么实现的? ①.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容; ②.每次扩展的时候,都是扩展2倍; ③.扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。 在putVal()中,我们看到在这个函数里面使用到了2次resize()方法,resize()方法表示的在进行第一次初始化时会对其进行扩容,或者当该数组的实际大小大于其临界值值(第一次为12),这个时候在扩容的同时也会伴随的桶上面的元素进行重新分发,这也是JDK1.8版本的一个优化的地方,在1.7中,扩容之后需要重新去计算其Hash值,根据Hash值对其进行分发,但在1.8版本中,则是根据在同一个桶的位置中进行判断(e.hash & oldCap)是否为0,重新进行hash分配后,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上 HashMap是怎么解决哈希冲突的? 答:在解决这个问题之前,我们首先需要知道什么是哈希冲突,而在了解哈希冲突之前我们还要知道什么是哈希才行; 什么是哈希? Hash,一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。 所有散列函数都有如下一个基本特性**:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同**。 什么是哈希冲突? 当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。 HashMap的数据结构 在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做链地址法的方式可以解决哈希冲突: 这样我们就可以将拥有相同哈希值的对象组织成一个链表放在hash值所对应的bucket下,但相比于hashCode返回的int类型,我们HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要远小于int类型的范围,所以我们如果只是单纯的用hashCode取余来获取对应的bucket这将会大大增加哈希碰撞的概率,并且最坏情况下还会将HashMap变成一个单链表,所以我们还需要对hashCode作一定的优化 hash()函数 上面提到的问题,主要是因为如果使用hashCode取余,那么相当于参与运算的只有hashCode的低位,高位是没有起到任何作用的,所以我们的思路就是让hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动,在JDK 1.8中的hash()函数如下: static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与自己右移16位进行异或运算(高低位异或) } 这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动); JDK1.8新增红黑树 通过上面的链地址法(使用散列表)和扰动函数我们成功让我们的数据分布更平均,哈希碰撞减少,但是当我们的HashMap中存在大量数据时,加入我们某个bucket下对应的链表有n个元素,那么遍历时间复杂度就为O(n),为了针对这个问题,JDK1.8在HashMap中新增了红黑树的数据结构,进一步使得遍历复杂度降低至O(logn); 总结 简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的: 使用链地址法(使用散列表)来链接拥有相同hash值的数据;使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;引入红黑树进一步降低遍历的时间复杂度,使得遍历更快; **能否使用任何类作为 Map 的 key? **可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点: 如果类重写了 equals() 方法,也应该重写 hashCode() 方法。 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。 如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。 用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode() 和 equals() 在未来不会改变,这样就会解决与可变相关的问题了。 为什么HashMap中String、Integer这样的包装类适合作为K? 答:String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况 内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况; 如果使用Object作为HashMap的Key,应该怎么办呢? 答:重写hashCode()和equals()方法 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性; HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标 答:hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置; 那怎么解决呢? HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均; 在保证数组长度为2的幂次方的时候,使用hash()运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题; HashMap 的长度为什么是2的幂次方 为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。 这个算法应该如何设计呢? 我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。 那为什么是两次扰动呢? 答:这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的; HashMap 与 HashTable 有什么区别? 线程安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它; 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。 **初始容量大小和每次扩充容量大小的不同 **: ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。 如何决定使用 HashMap 还是 TreeMap? 对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。 HashMap 和 ConcurrentHashMap 的区别 ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。) HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。 ConcurrentHashMap 和 Hashtable 的区别? ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; 实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 两者的对比图: HashTable: JDK1.7的ConcurrentHashMap: JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点): 答:ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。 ConcurrentHashMap 底层具体实现知道吗?实现原理是什么? JDK1.7 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下: 一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。 JDK1.8 在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。 结构如下: 如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的个数达到8个,则通过treeifyBin方法转化为红黑树,如果oldVal不为空,说明是一次更新操作,没有对元素个数产生影响,则直接返回旧值;如果插入的是一个新节点,则执行addCount()方法尝试更新元素个数baseCount; 辅助工具类 Array 和 ArrayList 有何区别? Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。 如何实现 Array 和 List 之间的转换? Array 转 List: Arrays. asList(array) ;List 转 Array:List 的 toArray() 方法。 comparable 和 comparator的区别? comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序 一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort(). 方法如何比较元素? TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。 Collections 工具类的 sort 方法有两种重载的形式, 第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较; 第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。
剑曼红尘 2020-03-24 14:41:57 0 浏览量 回答数 0

问题

【Java学习全家桶】1460道Java热门问题,阿里百位技术专家答疑解惑

阿里极客公益活动: 或许你挑灯夜战只为一道难题 或许你百思不解只求一个答案 或许你绞尽脑汁只因一种未知 那么他们来了,阿里系技术专家来云栖问答为你解答技术难题了 他们用户自己手中的技术来帮助用户成长 本次活动特邀百位阿里技术专家对Java常...
管理贝贝 2019-12-01 20:07:15 27612 浏览量 回答数 19

回答

Android平台进行数据存储的五大方式,分别如下: 1 使用SharedPreferences存储数据 2 文件存储数据 3 SQLite数据库存储数据 4 使用ContentProvider存储数据 5 网络存储数据 下面详细讲解这五种方式的特点 第一种: 使用SharedPreferences存储数据 适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口 令密码等 核心原理:保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。通过DDMS的File Explorer面板,展开文件浏览树,很明显SharedPreferences数据总是存储在/data/data/<package name>/shared_prefs目录下。SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过SharedPreferences.edit()获取的内部接口Editor对象实现。 SharedPreferences本身是一 个接口,程序无法直接创建SharedPreferences实例,只能通过Context提供的getSharedPreferences(String name, int mode)方法来获取SharedPreferences实例,该方法中name表示要操作的xml文件名,第二个参数具体如下: Context.MODE_PRIVATE: 指定该SharedPreferences数据只能被本应用程序读、写。 Context.MODE_WORLD_READABLE: 指定该SharedPreferences数据能被其他应用程序读,但不能写。 Context.MODE_WORLD_WRITEABLE: 指定该SharedPreferences数据能被其他应用程序读,写 Editor有如下主要重要方法: SharedPreferences.Editor clear():清空SharedPreferences里所有数据 SharedPreferences.Editor putXxx(String key , xxx value): 向SharedPreferences存入指定key对应的数据,其中xxx 可以是boolean,float,int等各种基本类型据 SharedPreferences.Editor remove(): 删除SharedPreferences中指定key对应的数据项 boolean commit(): 当Editor编辑完成后,使用该方法提交修改 实际案例:运行界面如下 这里只提供了两个按钮和一个输入文本框,布局简单,故在此不给出界面布局文件了,程序核心代码如下: 、class ViewOcl implements View.OnClickListener{ @Override public void onClick(View v) { switch(v.getId()){ case R.id.btnSet: //步骤1:获取输入值 String code = txtCode.getText().toString().trim(); //步骤2-1:创建一个SharedPreferences.Editor接口对象,lock表示要写入的XML文件名,MODE_WORLD_WRITEABLE写操作 SharedPreferences.Editor editor = getSharedPreferences("lock", MODE_WORLD_WRITEABLE).edit(); //步骤2-2:将获取过来的值放入文件 editor.putString("code", code); //步骤3:提交 editor.commit(); Toast.makeText(getApplicationContext(), "口令设置成功", Toast.LENGTH_LONG).show(); break; case R.id.btnGet: //步骤1:创建一个SharedPreferences接口对象 SharedPreferences read = getSharedPreferences("lock", MODE_WORLD_READABLE); //步骤2:获取文件中的值 String value = read.getString("code", ""); Toast.makeText(getApplicationContext(), "口令为:"+value, Toast.LENGTH_LONG).show(); break; } } } 、读写其他应用的SharedPreferences: 步骤如下 1、在创建SharedPreferences时,指定MODE_WORLD_READABLE模式,表明该SharedPreferences数据可以被其他程序读取 2、创建其他应用程序对应的Context: Context pvCount = createPackageContext("com.tony.app", Context.CONTEXT_IGNORE_SECURITY);这里的com.tony.app就是其他程序的包名 3、使用其他程序的Context获取对应的SharedPreferences SharedPreferences read = pvCount.getSharedPreferences("lock", Context.MODE_WORLD_READABLE); 4、如果是写入数据,使用Editor接口即可,所有其他操作均和前面一致。 SharedPreferences对象与SQLite数据库相比,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。但是SharedPreferences也有其自身缺陷,比如其职能存储boolean,int,float,long和String五种简单的数据类型,比如其无法进行条件查询等。所以不论SharedPreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如SQLite数据库这样的其他数据存储方式。 第二种: 文件存储数据 核心原理: Context提供了两个方法来打开数据文件里的文件IO流 FileInputStream openFileInput(String name); FileOutputStream(String name , int mode),这两个方法第一个参数 用于指定文件名,第二个参数指定打开文件的模式。具体有以下值可选: MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可 以使用Context.MODE_APPEND MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。 MODE_WORLD_READABLE:表示当前文件可以被其他应用读取; MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。 除此之外,Context还提供了如下几个重要的方法: getDir(String name , int mode):在应用程序的数据文件夹下获取或者创建name对应的子目录 File getFilesDir():获取该应用程序的数据文件夹得绝对路径 String[] fileList():返回该应用数据文件夹的全部文件 public String read() { try { FileInputStream inStream = this.openFileInput("message.txt"); byte[] buffer = new byte[1024]; int hasRead = 0; StringBuilder sb = new StringBuilder(); while ((hasRead = inStream.read(buffer)) != -1) { sb.append(new String(buffer, 0, hasRead)); } inStream.close(); return sb.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } public void write(String msg){ // 步骤1:获取输入值 if(msg == null) return; try { // 步骤2:创建一个FileOutputStream对象,MODE_APPEND追加模式 FileOutputStream fos = openFileOutput("message.txt", MODE_APPEND); // 步骤3:将获取过来的值放入文件 fos.write(msg.getBytes()); // 步骤4:关闭数据流 fos.close(); } catch (Exception e) { e.printStackTrace(); } } openFileOutput()方法的第一参数用于指定文件名称,不能包含路径分隔符“/” ,如果文件不存在,Android 会自动创建它。创建的文件保存在/data/data//files目录,如: /data/data/cn.tony.app/files/message.txt, 下面讲解某些特殊文件读写需要注意的地方: 读写sdcard上的文件 其中读写步骤按如下进行: 1、调用Environment的getExternalStorageState()方法判断手机上是否插了sd卡,且应用程序具有读写SD卡的权限,如下代码将返回true Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) 2、调用Environment.getExternalStorageDirectory()方法来获取外部存储器,也就是SD卡的目录,或者使用"/mnt/sdcard/"目录 3、使用IO流操作SD卡上的文件 注意点:手机应该已插入SD卡,对于模拟器而言,可通过mksdcard命令来创建虚拟存储卡 必须在AndroidManifest.xml上配置读写SD卡的权限 // 文件写操作函数 private void write(String content) { if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { // 如果sdcard存在 File file = new File(Environment.getExternalStorageDirectory() .toString() + File.separator + DIR + File.separator + FILENAME); // 定义File类对象 if (!file.getParentFile().exists()) { // 父文件夹不存在 file.getParentFile().mkdirs(); // 创建文件夹 } PrintStream out = null; // 打印流对象用于输出 try { out = new PrintStream(new FileOutputStream(file, true)); // 追加文件 out.println(content); } catch (Exception e) { e.printStackTrace(); } finally { if (out != null) { out.close(); // 关闭打印流 } } } else { // SDCard不存在,使用Toast提示用户 Toast.makeText(this, "保存失败,SD卡不存在!", Toast.LENGTH_LONG).show(); } } // 文件读操作函数 private String read() { if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { // 如果sdcard存在 File file = new File(Environment.getExternalStorageDirectory() .toString() + File.separator + DIR + File.separator + FILENAME); // 定义File类对象 if (!file.getParentFile().exists()) { // 父文件夹不存在 file.getParentFile().mkdirs(); // 创建文件夹 } Scanner scan = null; // 扫描输入 StringBuilder sb = new StringBuilder(); try { scan = new Scanner(new FileInputStream(file)); // 实例化Scanner while (scan.hasNext()) { // 循环读取 sb.append(scan.next() + "\n"); // 设置文本 } return sb.toString(); } catch (Exception e) { e.printStackTrace(); } finally { if (scan != null) { scan.close(); // 关闭打印流 } } } else { // SDCard不存在,使用Toast提示用户 Toast.makeText(this, "读取失败,SD卡不存在!", Toast.LENGTH_LONG).show(); } return null; } 复制代码 第三种:SQLite存储数据 SQLite是轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧 SQLiteDatabase类为我们提供了很多种方法,上面的代码中基本上囊括了大部分的数据库操作;对于添加、更新和删除来说,我们都可以使用 1 db.executeSQL(String sql); 2 db.executeSQL(String sql, Object[] bindArgs);//sql语句中使用占位符,然后第二个参数是实际的参数集 除了统一的形式之外,他们还有各自的操作方法: 1 db.insert(String table, String nullColumnHack, ContentValues values); 2 db.update(String table, Contentvalues values, String whereClause, String whereArgs); 3 db.delete(String table, String whereClause, String whereArgs);以上三个方法的第一个参数都是表示要操作的表名;insert中的第二个参数表示如果插入的数据每一列都为空的话,需要指定此行中某一列的名称,系统将此列设置为NULL,不至于出现错误;insert中的第三个参数是ContentValues类型的变量,是键值对组成的Map,key代表列名,value代表该列要插入的值;update的第二个参数也很类似,只不过它是更新该字段key为最新的value值,第三个参数whereClause表示WHERE表达式,比如“age > ? and age < ?”等,最后的whereArgs参数是占位符的实际参数值;delete方法的参数也是一样 下面给出demo 数据的添加 1.使用insert方法 复制代码1 ContentValues cv = new ContentValues();//实例化一个ContentValues用来装载待插入的数据2 cv.put("title","you are beautiful");//添加title3 cv.put("weather","sun"); //添加weather4 cv.put("context","xxxx"); //添加context5 String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")6 .format(new Date());7 cv.put("publish ",publish); //添加publish8 db.insert("diary",null,cv);//执行插入操作复制代码2.使用execSQL方式来实现 String sql = "insert into user(username,password) values ('Jack Johnson','iLovePopMuisc');//插入操作的SQL语句db.execSQL(sql);//执行SQL语句数据的删除 同样有2种方式可以实现 String whereClause = "username=?";//删除的条件String[] whereArgs = {"Jack Johnson"};//删除的条件参数db.delete("user",whereClause,whereArgs);//执行删除使用execSQL方式的实现 String sql = "delete from user where username='Jack Johnson'";//删除操作的SQL语句db.execSQL(sql);//执行删除操作数据修改 同上,仍是2种方式 ContentValues cv = new ContentValues();//实例化ContentValuescv.put("password","iHatePopMusic");//添加要更改的字段及内容String whereClause = "username=?";//修改条件String[] whereArgs = {"Jack Johnson"};//修改条件的参数db.update("user",cv,whereClause,whereArgs);//执行修改使用execSQL方式的实现 String sql = "update user set password = 'iHatePopMusic' where username='Jack Johnson'";//修改的SQL语句db.execSQL(sql);//执行修改数据查询 下面来说说查询操作。查询操作相对于上面的几种操作要复杂些,因为我们经常要面对着各种各样的查询条件,所以系统也考虑到这种复杂性,为我们提供了较为丰富的查询形式: 1 db.rawQuery(String sql, String[] selectionArgs); 2 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy); 3 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit); 4 db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit); 上面几种都是常用的查询方法,第一种最为简单,将所有的SQL语句都组织到一个字符串中,使用占位符代替实际参数,selectionArgs就是占位符实际参数集; 各参数说明: table:表名称colums:表示要查询的列所有名称集selection:表示WHERE之后的条件语句,可以使用占位符selectionArgs:条件语句的参数数组groupBy:指定分组的列名having:指定分组条件,配合groupBy使用orderBy:y指定排序的列名limit:指定分页参数distinct:指定“true”或“false”表示要不要过滤重复值Cursor:返回值,相当于结果集ResultSet最后,他们同时返回一个Cursor对象,代表数据集的游标,有点类似于JavaSE中的ResultSet。下面是Cursor对象的常用方法: 复制代码 1 c.move(int offset); //以当前位置为参考,移动到指定行 2 c.moveToFirst(); //移动到第一行 3 c.moveToLast(); //移动到最后一行 4 c.moveToPosition(int position); //移动到指定行 5 c.moveToPrevious(); //移动到前一行 6 c.moveToNext(); //移动到下一行 7 c.isFirst(); //是否指向第一条 8 c.isLast(); //是否指向最后一条 9 c.isBeforeFirst(); //是否指向第一条之前 10 c.isAfterLast(); //是否指向最后一条之后 11 c.isNull(int columnIndex); //指定列是否为空(列基数为0) 12 c.isClosed(); //游标是否已关闭 13 c.getCount(); //总数据项数 14 c.getPosition(); //返回当前游标所指向的行数 15 c.getColumnIndex(String columnName);//返回某列名对应的列索引值 16 c.getString(int columnIndex); //返回当前行指定列的值 复制代码实现代码 复制代码String[] params = {12345,123456};Cursor cursor = db.query("user",columns,"ID=?",params,null,null,null);//查询并获得游标if(cursor.moveToFirst()){//判断游标是否为空 for(int i=0;i<cursor.getCount();i++){ cursor.move(i);//移动到指定记录 String username = cursor.getString(cursor.getColumnIndex("username"); String password = cursor.getString(cursor.getColumnIndex("password")); } }复制代码通过rawQuery实现的带参数查询 复制代码Cursor result=db.rawQuery("SELECT ID, name, inventory FROM mytable");//Cursor c = db.rawQuery("s name, inventory FROM mytable where ID=?",new Stirng[]{"123456"}); result.moveToFirst(); while (!result.isAfterLast()) { int id=result.getInt(0); String name=result.getString(1); int inventory=result.getInt(2); // do something useful with these result.moveToNext(); } result.close();复制代码 在上面的代码示例中,已经用到了这几个常用方法中的一些,关于更多的信息,大家可以参考官方文档中的说明。 最后当我们完成了对数据库的操作后,记得调用SQLiteDatabase的close()方法释放数据库连接,否则容易出现SQLiteException。 上面就是SQLite的基本应用,但在实际开发中,为了能够更好的管理和维护数据库,我们会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法。 这里直接使用案例讲解:下面是案例demo的界面 SQLiteOpenHelper类介绍 SQLiteOpenHelper是SQLiteDatabase的一个帮助类,用来管理数据库的创建和版本的更新。一般是建立一个类继承它,并实现它的onCreate和onUpgrade方法。 方法名 方法描述SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version) 构造方法,其中 context 程序上下文环境 即:XXXActivity.this; name :数据库名字; factory:游标工厂,默认为null,即为使用默认工厂; version 数据库版本号 onCreate(SQLiteDatabase db) 创建数据库时调用onUpgrade(SQLiteDatabase db,int oldVersion , int newVersion) 版本更新时调用getReadableDatabase() 创建或打开一个只读数据库getWritableDatabase() 创建或打开一个读写数据库首先创建数据库类 复制代码 1 import android.content.Context; 2 import android.database.sqlite.SQLiteDatabase; 3 import android.database.sqlite.SQLiteDatabase.CursorFactory; 4 import android.database.sqlite.SQLiteOpenHelper; 5 6 public class SqliteDBHelper extends SQLiteOpenHelper { 7 8 // 步骤1:设置常数参量 9 private static final String DATABASE_NAME = "diary_db";10 private static final int VERSION = 1;11 private static final String TABLE_NAME = "diary";12 13 // 步骤2:重载构造方法14 public SqliteDBHelper(Context context) {15 super(context, DATABASE_NAME, null, VERSION);16 }17 18 /*19 * 参数介绍:context 程序上下文环境 即:XXXActivity.this 20 * name 数据库名字 21 * factory 接收数据,一般情况为null22 * version 数据库版本号23 */24 public SqliteDBHelper(Context context, String name, CursorFactory factory,25 int version) {26 super(context, name, factory, version);27 }28 //数据库第一次被创建时,onCreate()会被调用29 @Override30 public void onCreate(SQLiteDatabase db) {31 // 步骤3:数据库表的创建32 String strSQL = "create table "33 + TABLE_NAME34 + "(tid integer primary key autoincrement,title varchar(20),weather varchar(10),context text,publish date)";35 //步骤4:使用参数db,创建对象36 db.execSQL(strSQL);37 }38 //数据库版本变化时,会调用onUpgrade()39 @Override40 public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {41 42 }43 }复制代码正如上面所述,数据库第一次创建时onCreate方法会被调用,我们可以执行创建表的语句,当系统发现版本变化之后,会调用onUpgrade方法,我们可以执行修改表结构等语句。 我们需要一个Dao,来封装我们所有的业务方法,代码如下: 复制代码 1 import android.content.Context; 2 import android.database.Cursor; 3 import android.database.sqlite.SQLiteDatabase; 4 5 import com.chinasoft.dbhelper.SqliteDBHelper; 6 7 public class DiaryDao { 8 9 private SqliteDBHelper sqliteDBHelper;10 private SQLiteDatabase db;11 12 // 重写构造方法13 public DiaryDao(Context context) {14 this.sqliteDBHelper = new SqliteDBHelper(context);15 db = sqliteDBHelper.getWritableDatabase();16 }17 18 // 读操作19 public String execQuery(final String strSQL) {20 try {21 System.out.println("strSQL>" + strSQL);22 // Cursor相当于JDBC中的ResultSet23 Cursor cursor = db.rawQuery(strSQL, null);24 // 始终让cursor指向数据库表的第1行记录25 cursor.moveToFirst();26 // 定义一个StringBuffer的对象,用于动态拼接字符串27 StringBuffer sb = new StringBuffer();28 // 循环游标,如果不是最后一项记录29 while (!cursor.isAfterLast()) {30 sb.append(cursor.getInt(0) + "/" + cursor.getString(1) + "/"31 + cursor.getString(2) + "/" + cursor.getString(3) + "/"32 + cursor.getString(4)+"#");33 //cursor游标移动34 cursor.moveToNext();35 }36 db.close();37 return sb.deleteCharAt(sb.length()-1).toString();38 } catch (RuntimeException e) {39 e.printStackTrace();40 return null;41 }42 43 }44 45 // 写操作46 public boolean execOther(final String strSQL) {47 db.beginTransaction(); //开始事务48 try {49 System.out.println("strSQL" + strSQL);50 db.execSQL(strSQL);51 db.setTransactionSuccessful(); //设置事务成功完成 52 db.close();53 return true;54 } catch (RuntimeException e) {55 e.printStackTrace();56 return false;57 }finally { 58 db.endTransaction(); //结束事务 59 } 60 61 }62 }复制代码我们在Dao构造方法中实例化sqliteDBHelper并获取一个SQLiteDatabase对象,作为整个应用的数据库实例;在增删改信息时,我们采用了事务处理,确保数据完整性;最后要注意释放数据库资源db.close(),这一个步骤在我们整个应用关闭时执行,这个环节容易被忘记,所以朋友们要注意。 我们获取数据库实例时使用了getWritableDatabase()方法,也许朋友们会有疑问,在getWritableDatabase()和getReadableDatabase()中,你为什么选择前者作为整个应用的数据库实例呢?在这里我想和大家着重分析一下这一点。 我们来看一下SQLiteOpenHelper中的getReadableDatabase()方法: 复制代码 1 public synchronized SQLiteDatabase getReadableDatabase() { 2 if (mDatabase != null && mDatabase.isOpen()) { 3 // 如果发现mDatabase不为空并且已经打开则直接返回 4 return mDatabase; 5 } 6 7 if (mIsInitializing) { 8 // 如果正在初始化则抛出异常 9 throw new IllegalStateException("getReadableDatabase called recursively"); 10 } 11 12 // 开始实例化数据库mDatabase 13 14 try { 15 // 注意这里是调用了getWritableDatabase()方法 16 return getWritableDatabase(); 17 } catch (SQLiteException e) { 18 if (mName == null) 19 throw e; // Can't open a temp database read-only! 20 Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e); 21 } 22 23 // 如果无法以可读写模式打开数据库 则以只读方式打开 24 25 SQLiteDatabase db = null; 26 try { 27 mIsInitializing = true; 28 String path = mContext.getDatabasePath(mName).getPath();// 获取数据库路径 29 // 以只读方式打开数据库 30 db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY); 31 if (db.getVersion() != mNewVersion) { 32 throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to " 33 + mNewVersion + ": " + path); 34 } 35 36 onOpen(db); 37 Log.w(TAG, "Opened " + mName + " in read-only mode"); 38 mDatabase = db;// 为mDatabase指定新打开的数据库 39 return mDatabase;// 返回打开的数据库 40 } finally { 41 mIsInitializing = false; 42 if (db != null && db != mDatabase) 43 db.close(); 44 } 45 }复制代码在getReadableDatabase()方法中,首先判断是否已存在数据库实例并且是打开状态,如果是,则直接返回该实例,否则试图获取一个可读写模式的数据库实例,如果遇到磁盘空间已满等情况获取失败的话,再以只读模式打开数据库,获取数据库实例并返回,然后为mDatabase赋值为最新打开的数据库实例。既然有可能调用到getWritableDatabase()方法,我们就要看一下了: 复制代码public synchronized SQLiteDatabase getWritableDatabase() { if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { // 如果mDatabase不为空已打开并且不是只读模式 则返回该实例 return mDatabase; } if (mIsInitializing) { throw new IllegalStateException("getWritableDatabase called recursively"); } // If we have a read-only database open, someone could be using it // (though they shouldn't), which would cause a lock to be held on // the file, and our attempts to open the database read-write would // fail waiting for the file lock. To prevent that, we acquire the // lock on the read-only database, which shuts out other users. boolean success = false; SQLiteDatabase db = null; // 如果mDatabase不为空则加锁 阻止其他的操作 if (mDatabase != null) mDatabase.lock(); try { mIsInitializing = true; if (mName == null) { db = SQLiteDatabase.create(null); } else { // 打开或创建数据库 db = mContext.openOrCreateDatabase(mName, 0, mFactory); } // 获取数据库版本(如果刚创建的数据库,版本为0) int version = db.getVersion(); // 比较版本(我们代码中的版本mNewVersion为1) if (version != mNewVersion) { db.beginTransaction();// 开始事务 try { if (version == 0) { // 执行我们的onCreate方法 onCreate(db); } else { // 如果我们应用升级了mNewVersion为2,而原版本为1则执行onUpgrade方法 onUpgrade(db, version, mNewVersion); } db.setVersion(mNewVersion);// 设置最新版本 db.setTransactionSuccessful();// 设置事务成功 } finally { db.endTransaction();// 结束事务 } } onOpen(db); success = true; return db;// 返回可读写模式的数据库实例 } finally { mIsInitializing = false; if (success) { // 打开成功 if (mDatabase != null) { // 如果mDatabase有值则先关闭 try { mDatabase.close(); } catch (Exception e) { } mDatabase.unlock();// 解锁 } mDatabase = db;// 赋值给mDatabase } else { // 打开失败的情况:解锁、关闭 if (mDatabase != null) mDatabase.unlock(); if (db != null) db.close(); } } }复制代码大家可以看到,几个关键步骤是,首先判断mDatabase如果不为空已打开并不是只读模式则直接返回,否则如果mDatabase不为空则加锁,然后开始打开或创建数据库,比较版本,根据版本号来调用相应的方法,为数据库设置新版本号,最后释放旧的不为空的mDatabase并解锁,把新打开的数据库实例赋予mDatabase,并返回最新实例。 看完上面的过程之后,大家或许就清楚了许多,如果不是在遇到磁盘空间已满等情况,getReadableDatabase()一般都会返回和getWritableDatabase()一样的数据库实例,所以我们在DBManager构造方法中使用getWritableDatabase()获取整个应用所使用的数据库实例是可行的。当然如果你真的担心这种情况会发生,那么你可以先用getWritableDatabase()获取数据实例,如果遇到异常,再试图用getReadableDatabase()获取实例,当然这个时候你获取的实例只能读不能写了 最后,让我们看一下如何使用这些数据操作方法来显示数据,界面核心逻辑代码: 复制代码public class SQLiteActivity extends Activity { public DiaryDao diaryDao; //因为getWritableDatabase内部调用了mContext.openOrCreateDatabase(mName, 0, mFactory); //所以要确保context已初始化,我们可以把实例化Dao的步骤放在Activity的onCreate里 @Override protected void onCreate(Bundle savedInstanceState) { diaryDao = new DiaryDao(SQLiteActivity.this); initDatabase(); } class ViewOcl implements View.OnClickListener { @Override public void onClick(View v) { String strSQL; boolean flag; String message; switch (v.getId()) { case R.id.btnAdd: String title = txtTitle.getText().toString().trim(); String weather = txtWeather.getText().toString().trim();; String context = txtContext.getText().toString().trim();; String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(new Date()); // 动态组件SQL语句 strSQL = "insert into diary values(null,'" + title + "','" + weather + "','" + context + "','" + publish + "')"; flag = diaryDao.execOther(strSQL); //返回信息 message = flag?"添加成功":"添加失败"; Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); break; case R.id.btnDelete: strSQL = "delete from diary where tid = 1"; flag = diaryDao.execOther(strSQL); //返回信息 message = flag?"删除成功":"删除失败"; Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); break; case R.id.btnQuery: strSQL = "select * from diary order by publish desc"; String data = diaryDao.execQuery(strSQL); Toast.makeText(getApplicationContext(), data, Toast.LENGTH_LONG).show(); break; case R.id.btnUpdate: strSQL = "update diary set title = '测试标题1-1' where tid = 1"; flag = diaryDao.execOther(strSQL); //返回信息 message = flag?"更新成功":"更新失败"; Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); break; } } } private void initDatabase() { // 创建数据库对象 SqliteDBHelper sqliteDBHelper = new SqliteDBHelper(SQLiteActivity.this); sqliteDBHelper.getWritableDatabase(); System.out.println("数据库创建成功"); } }复制代码 Android sqlite3数据库管理工具 Android SDK的tools目录下提供了一个sqlite3.exe工具,这是一个简单的sqlite数据库管理工具。开发者可以方便的使用其对sqlite数据库进行命令行的操作。 程序运行生成的.db文件一般位于"/data/data/项目名(包括所处包名)/databases/.db",因此要对数据库文件进行操作需要先找到数据库文件: 1、进入shell 命令 adb shell2、找到数据库文件 cd data/data ls --列出所有项目 cd project_name --进入所需项目名 cd databases ls --列出现寸的数据库文件 3、进入数据库 sqlite3 test_db --进入所需数据库 会出现类似如下字样: SQLite version 3.6.22Enter ".help" for instructionsEnter SQL statements terminated with a ";"sqlite>至此,可对数据库进行sql操作。 4、sqlite常用命令 .databases --产看当前数据库.tables --查看当前数据库中的表.help --sqlite3帮助.schema --各个表的生成语句 原文地址https://www.cnblogs.com/ITtangtang/p/3920916.html
auto_answer 2019-12-02 01:50:21 0 浏览量 回答数 0

云产品推荐

上海奇点人才服务相关的云产品 小程序定制 上海微企信息技术相关的云产品 国内短信套餐包 ECS云服务器安全配置相关的云产品 开发者问答 阿里云建站 自然场景识别相关的云产品 万网 小程序开发制作 视频内容分析 视频集锦 代理记账服务