JDBC的全称是Java DataBase Connection,也就是Java数据库连接,我们可以⽤它来操作关系型数据库。JDBC接⼝及相关类在java.sql 包和javax.sql包⾥。我们可以⽤它来连接数据库,执⾏SQL查询,存储过程,并处理返回的结果。
JDBC接⼝让Java程序和JDBC驱动实现了松耦合,使得切换不同的数据库变得更加简单。
1.com.mysql.cj.jdbc.Driver是Driver驱动所在的位置,加载驱动
2.Class.forName()是一个反射,但是他没有返回一个Class对象,因为我们不需要;
这是Driver的代码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
它除了构造方法,就只有一个静态代码块,当我们反射进行的时候,这个类就开始初始化,他的静态代码块内容就已经被执行了,我们真正需要的是DriverManager.registerDriver(new Driver());这一行代码
是一个工厂类,我们通过它来创建数据库连接,当JDBC的Driver类被加载进来时,它会自己注册到DriverManager类里面
1.注册驱动
2.获取连接
3.创建一个Statement语句对象
4.执行SQL语句
5.处理结果集
6.关闭资源
1.PreparedStatement 继承于 Statement,Statement 一般用于执行固定的没有参数的SQL。2.PreparedStatement 一般用于执行有?参数预编译的SQL语句。可以防止SQL注入,安全性高于Statement。3.CallableStatement适用于执行存储过程。
1)Statement的execute(String query)⽅法⽤来执⾏任意的SQL查询,如果查询的结果是⼀个ResultSet,这个⽅法就返回true。如果结果不是ResultSet,⽐如insert或者update查询,它就会返回false。
2)Statement的executeQuery(String query)接⼝⽤来执⾏select查询,并且返回ResultSet。即使查询不到记录返回的ResultSet也不会为null。我们通常使⽤executeQuery来执⾏查询语句,这样的话如果传进来的是insert或者update语句的 话,它会抛出错误信息为“executeQuery method can not be used for update”的java.util.SQLException。
3)Statement的executeUpdate(String query)⽅法⽤来执⾏insert或者update/delete(DML)语句。
4)只有当你不确定是什么语句的时候才应该使⽤execute()⽅法,否则应该使⽤executeQuery或者executeUpdate⽅法。
最好的办法是利用sql语句进行分页,这样每次查询出的结果集中就只包含某页的数据内容。
sql语句分页,不同的数据库下的分页方案各不一样,假设一共有38条数据,每页有10条数据,查询第3页的数据,下面是主流的三种数据库的分页sql:
Oracle:
select * from
(select *,rownum as tempid from student ) t
where t.tempid between 20 and 30;
mysql:
select * from students limit 20,10;
sql server:
select top 10 * from students where id not in
(select top 20 id from students order by id)
order by id;
事务是作为单个逻辑⼯作单元执⾏的⼀系列操作,⼀个逻辑⼯作单元必须有四个属性,称为原⼦性、⼀致性、隔离性和持久性(ACID) 属性,只有这样才能成为⼀个事务 。JDBC处理事务有如下操作:
conn.setAutoComit(false);设置提交⽅式为⼿⼯提交。
conn.commit()提交事务。
conn.rollback()回滚事务。
提交与回滚只选择⼀个执⾏。正常情况下提交事务,如果出现异常,则回滚。
数据库连接是⼀种关键的有限的昂贵的资源,对数据库连接的管理能显著影响到整个应⽤程序的伸缩性和健壮性,影响到程序 的性能指标。数据库连接池正是针对这个问题提出来的。
数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,⽽不是重新建⽴⼀个;释放空闲时间超过最⼤空闲时间的数据库连接来避免因为没有释放数据库连接⽽引起的数据库连接遗漏。这项技术能明显提⾼对数据库操作的性能。
数据库连接池在初始化时将创建⼀定数量的数据库连接放到连接池中,这些数据库连接的数量是由最⼩数据库连接数来设定的。⽆论这些数据库连接是否被使⽤,连接池都将⼀直保证⾄少拥有这么多的连接数量。连接池的最⼤数据库连接数量限定了这个连接池能占有的最⼤连接数,当应⽤程序向连接池请求的连接数超过最⼤连接数量时,这些请求将被加⼊到等待队列中。
桥接模式,首先DriverManager获得Connection是通过反射和类加载机制从数据库驱动包的driver中拿到连接,所以这里真正参与桥接模式的是driver,而DriverManager和桥接模式没有关系,DriverManager只是对driver的一个管理器。而我们作为使用者只去关心Connection,不会去关心driver,因为我们的操作都是通过操作Connection来实现的。这样分析下来这个桥接就清晰了逻辑——java.sql.Driver作为抽象桥类,而驱动包如com.mysql.jdbc.Driver具体的实现桥接类,而Connection是被桥接的对象。
默认情况下,我们创建的数据库连接,是工作在自动提交的模式下的。这意味着只要我们执行完一条查询语句,就会自动进行提交。因此我们的每条查询,实际上都是一个事务,如果我们执行的是DML或者DDL,每条语句完成的时候,数据库就已经完成修改了。有的时候我们希望由一组SQL查询组成一个事务,如果它们都执行OK我们再进行提交,如果中途出现异常了,我们可以进行回滚。
JDBC接口提供了一个setAutoCommit(boolean flag)方法,我们可以用它来关闭连接自动提交的特性。我们应该在需要手动提交时才关闭这个特性,不然的话事务不会自动提交,每次都得手动提交。数据库 通过表锁来管理事务,这个操作非常消耗资源。因此我们应当完成操作后尽快的提交事务。在这里有更多关于事务的示例程序。
CLOB意思是Character Large OBjects,字符大对象,它是由单字节字符组成的字符串数据,有自己专门的代码页。这种数据类型适用于存储超长的文本信息,那些可能会超出标准的VARCHAR数据类型长度限制(上限是32KB)的文本。
BLOB是Binary Larget OBject,它是二进制大对象,由二进制数据组成,没有专门的代码页。它能用于存储超过VARBINARY限制(32KB)的二进制数据。这种数据类型适合存储图片,声音,图形,或者其它业务程序特定的数据。
每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 Class.forName,这种方式来控制类的加载,该方法会返回一个 Class 对象。
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:
(1)Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
(2)Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
(3)Constructor :可以用 Constructor 创建新的对象。
应用举例:工厂模式,使用反射机制,根据全限定类名获得某个类的 Class 实例。
反射是用来描述类的信息的。对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
class:用来描述类本身
Packge:用来描述类所属的包
Field:用来描述类中的属性
Method:用来描述类中的方法
Constructor:用来描述类中的构造方法
Annotation:用来描述类中的注解
1)Class clazz=class.forName("包名.类名")
2)Class clazz=类名.class;
3)Class clazz=对象.getClass();
(1)获取类的权限修饰符--------->int result=getModifiers();
(2)获取名字------------>string name=clazz.getName();
(3)获取包名------------>Packge p=clazz.getPackge();
(4)寻找clazz中无参数构造方法:Clazz.getConstructor([String.class]);
执行构造方法创建对象:Con.newInstance([参数]);
(5)Field c=cls.getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
Field c=cls.getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和 proteced,但是不包括父类的声明字段。
(1)反射得经典用法就是在xml或者properties配置文件中,然后在java类里面区解析这些内容,得到一个字符串,然后通过反射机制,通过这些字符串获得某个类得class实例,这样的话就可以动态的配置一些东西,而不需要每次都重新去new,要改的话也是直接改配置文件,代码维护起来方便很多。
(2)当你在做一个软件开发的插件的时候,你连插件的类型名称都不知道,你怎么实例化这个对象呢?因为程序是支持插件的(第三方的),在开发的时候并不知道 。所以无法在代码中 New出来 ,但反射可以,通过反射,动态加载程序集,然后读出类,检查标记之后再实例化对象,就可以获得正确的类实例。
(3)在编码阶段不知道那个类名,要在运行期从配置文件读取类名, 这时候就没有办法硬编码new ClassName(),而必须用到反射才能创建这个对象.反射的目的就是为了扩展未知的应用。比如你写了一个程序,这个程序定义了一些接口,只要实现了这些接口的dll都可以作为插件来插入到这个程序中。那么怎么实现呢?就可以通过反射来实现。就是把dll加载进内存,然后通过反射的方式来调用dll中的方法。很多工厂模式就是使用的反射。
getSimpleName:只获取类名
getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。
getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。
可以;必须只有一个类名与文件名相同。
byte 的取值范围是 -128 -> 127 之间,一共是 256 位。一个 byte 类型在计算机中占据一个字节,那么就是 8 bit,所以最大就是 2^7 = 1111 1111。
Java 中用补码来表示二进制数,补码的最高位是符号位,最高位用 0 表示正数,最高位 1 表示负数,正数的补码就是其本身,由于最高位是符号位,所以正数表示的就是 0111 1111 ,也就是 127。最大负数就是 1111 1111,这其中会涉及到两个 0 ,一个 +0 ,一个 -0 ,+0 归为正数,也就是 0 ,-0 归为负数,也就是 -128,所以 byte 的范围就是 -128 – 127。
在最外层循环前加一个标记如outfor,然后用break outfor;可以跳出多重循环。例如以下代码:
public class TestBreak {
public static void main(String[] args) {
outfor: for (int i = 0; i < 10; i++){
for (int j = 0; j < 10; j++){
if (j == 5){
break outfor;
}
System.out.println("j = " + j);
}
}
}
}
运行结果如下所示:
j = 0
j = 1
j = 2
j = 3
j = 4
早期的 JDK 中,switch(expr)中,expr 可以是 byte、short、char、int。从 1.5 版开始,Java 中引入了枚举类型(enum),expr 也可以是枚举,从 JDK 1.7 版开始,还可以是字符串(String)。长整型(long)是不可以的。
&运算符是:逻辑与;&&运算符是:短路与。
1)&和&&在程序中最终的运算结果是完全一致的,只不过&&存在短路现象。如果是&运算符,那么不管左边的表达式是true还是false,右边表达式是一定会执行的。当&&运算符左边的表达式结果为false的时候,右边的表达式不执行,此时就发生了短路现象,也就是说&&会更加的智能。这就是他们俩的本质区别。
2)当然,&运算符还可以使用在二进制位运算上,例如按位与操作。
char 类型可以存储一个中文汉字,因为Java中使用的编码是Unicode编码,一个char 类型占2个字节(16 比特),所以放一个中文是没问题的。
补充:使用Unicode 意味着字符在JVM内部和外部有不同的表现形式,在JVM内部都是 Unicode,当这个字符被从JVM内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以 Java 中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如 InputStreamReader和OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务。
早期的 JDK 中,switch(expr)中,expr 可以是 byte、short、char、int。从 1.5 版开始,Java 中引入了枚举类型(enum),expr 也可以是枚举,从 JDK 1.7 版开始,还可以是字符串(String)。长整型(long)是不可以的。
Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加0.5然后进行取整。
前者不正确,后者正确。
对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。
而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short)(s1 + 1);其中有隐含的强制类型转换。
Java中的数组没有length()方法,但是有length属性。String有length()方法。
2 << 3,将2左移3位
public class Test{
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150; System.out.println(f1 == f2);
System.out.println(f3 == f4);
}
}
f1==f2的结果是 true,而f3==f4 的结果是false。为什么呢?先来说说装箱的本质。当我们给一个Integer 对象赋一个 int 值的时候,会调用 Integer 类的静态方法 valueOf,如果看看valueOf的源代码就知道发生了什么。如果整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1==f2的结果是 true,而f3==f4 的结果是false。
Java 的JDK从 1.5 开始引入了自动装箱/拆箱机制。它为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是 Integer,其它基本类型对应的包装类如下:
原始类型: boolean,char,byte,short,int,long,float,double包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能 产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!
调用数值类型相应包装类中的方法 parse***(String)或 valueOf(String) 即可返回相应基本类型或包装类型数值;
将数字与空字符串相加即可获得其所对应的字符串;另外对于基本类型 数字还可调用 String 类中的 valueOf(…)方法返回相应字符串,而对于包装类型数字则可调用其 toString()方法获得相应字符串;
可用该数字构造一 java.math.BigDecimal 对象,再利用其 round()方法 进行四舍五入到保留小数点后两位,再将其转换为字符串截取最后两位。
false,因为有些浮点数不能完全精确的表示出来。
java中有三种移位运算符
<< :左移运算符,x << 1,相当于x乘以2(不溢出的情况下),低位补0
>> :带符号右移,x >> 1,相当于x除以2,正数高位补0,负数高位补1
>>> :无符号右移,忽略符号位,空位都以0补齐
泛型中类型擦除 Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。
注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要的作用有以下四方面: 生成文档,通过代码里标识的元数据生成javadoc文档。 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。
Java自带的标准注解,包括@Override、@Deprecated和@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
元注解:元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented @Retention用于标明注解被保留的阶段 @Target用于标明注解使用的范围 @Inherited用于标明注解可继承 @Documented用于标明是否生成javadoc文档 自定义注解,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。
整体上是封装、继承、多态、抽象。
首先面向对象是一种思想。在java中万事万物皆对象。类是对相同事物的一种抽象、是不可见的,对象具体的、可见的。由对象到类的过程是抽象的过程,由类到对象的过程是实例化的过程。面向对象的三大特征分别是封装、继承和多态。
封装隐藏了类的内部实现机制,对外界而言它的内部细节是隐藏的,暴露给外界的只是它的访问方法。例如在属性的修饰符上我们往往用的private私有的,这样其它类要想访问就通过get和set方法。因此封装可以程序员按照既定的方式调用方法,不必关心方法的内部实现,便于使用; 便于修改,增强 代码的可维护性。
继承在本质上是特殊~一般的关系,即常说的is-a关系。子类继承父类,表明子类是一种特殊的父类,并且具有父类所不具有的一些属性或方法。比如从猫类、狗类中可以抽象出一个动物类,具有和猫、狗、虎类的共同特性(吃、跑、叫等)。通过extends关键字来实现继承。Java中的继承是单继承,即一个子类只允许有一个父类。
Java多态是指的是首先两个类有继承关系,其次子类重写了父类的方法,最后父类引用指向子类对象。如Animal a=new Dog();这行代码就体现了多态。
Java中的多态靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程 序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存 里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
名字与类名相同;
没有返回值,但不能用void声明构造函数;
生成类的对象时自动执行,无需调用。
构造器不能被继承,因此不能被重写,但可以被重载。
super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一 个父类。
super也有三种用法:
1.普通的直接引用
与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。
2.子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分
class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
class StudentextendsPerson{
private String name;
publicStudent(String name, String name1){
super(name);
this.name = name1;
}
public void getInfo(){
System.out.println(this.name);
System.out.println(super.name);
}
}
public class Test{
public static void main(String[] args){
Student s1 = new Student("Father", "Child");
s1.getInfo();
}
}
3.引用父类构造函数
super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。
super:它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员
数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名 (实参)
this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
super()和this()区别是
[1]super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
[2]super()和this()均需放在构造方法内第一行。尽管可以用this调用一个构造器,但却不能调用两个。
[3]this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语 句,就失去了语句的意义,编译器也不会通过。
[4]this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变
量,static方法,static语句块。从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
方法的重载和重写本质都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态。
方法重载的规则:
1)方法名一致,
2)参数列表不同(参数顺序不同或者参数类型不同或者参数个数不同)。
3)重载与方法的返回值无关,这个很关键。
方法重写的规则:
1)参数列表和返回值类型必须完全与父类的方法一致
2)构造方法不能被重写,声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,但是能够被再次声明。
3)访问权限不能比父类中被重写的方法的访问权限更低。
4)重写的方法能够抛出任何检查异常(编译时异常),但是重写的方法不能抛出比被重写方法声明的更广泛的运行时异常。
1)接口中的所有方法都是抽象的,而抽象类可以有抽象方法,也可以有实例方法。
2)类需要继承,接口需要实现。一个类可以实现多个接口,但只能继承一个父类但接口却可以继承多接口。
3)接口与实现它的类不构成继承体系,即接口不是类体系的一部分。因此,不相关的类也可以实现相同的接口,而抽象类是属于类的继承体系,并且一般位于类体系的顶层。
接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承具体类,但前提是具体类必须有明确的构造函数。
值传递是指在调用函数时将实际参数复制一份到函数中,这样的话如果函数对其传递过来的形式参数进行修改,将不会影响到实际参数。
引用传递是指在调用函数时将对象的地址直接传递到函数中,如果在对形式参数进行修改,将影响到实际参数的值。
equals 和== 最大的区别是一个是方法一个是运算符。
1)基本类型中,==比较的是数值是否相等。equals方法是不能用于基本数据类型数据比较的,因为基本数据类型压根就没有方法。
2)引用类型中,==比较的是对象的地址值是否相等。equals方法比较的是引用类型的变量所指向的对象的地址是否相等。应为String这个类重写了equals方法,比较的是字符串的内容。
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码 的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就 意味着Java中的任何类都包含有hashCode()函数。
在Java中,每个对象都可以调用自己的hashCode方法得到自己的哈希值(hashCode),相当于对象的指纹信息,通常说世界上没有完全一样的指纹,但是在Java中没有这么绝对,我们依然可以用hashCode值来做一些提前的判断。
1)如果两个对象的hashCode值不一样,那么他们肯定是不同的两个对象;
2)如果两个对象的hashCode值一样,也不代表就是同一个对象;
3)如果两个对象的equals方法相等,那么他们的hashCode值一定相等。
在Java的一些集合类的实现中,在比较两个对象的值是否相等的时候,会根据上面的基本原则,先调用对象的hashCode值来进行比较,如果hashCode值不一样,就可以认定这是两个不一样的数据,如果hashCode值相同,我们会进一步调用equals()方法进行内容的比较。
equals 方法是用来比较对象大小是否相等的方法,hashcode 方法是用来判断每个对象 hash 值的一种方法。如果只重写 equals 方法而不重写 hashcode 方法,很可能会造成两个不同的对象,它们的 hashcode 也相等,造成冲突。
例如:String str1 = "通话"; String str2 = "重地";
它们两个的 hashcode 相等,但是 equals 可不相等。
不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。
Java对于eqauls方法和hashCode方法是这样规定的:
(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
(2)如果两个对象的hashCode相同,它们并不一定相同。
当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对 象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如 果哈希码频繁的冲突将会造成存取性能急剧下降)。
都不能。
1)抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。
2)本地方法是由本地代码(如 C++ 代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。
3)synchronized 和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。
修饰类:当用final修饰一个类时,表明这个类不能被继承。正如String类是不能被继承的。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
修饰方法:使用final修饰方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。因此,只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final。(注:一个类中的private方法会隐式地被指定为final方法)
修饰变量:对于被final修饰的变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
public class Demo1 {
public static void main(String[] args) {
MyClass myClass1 = new MyClass();
MyClass myClass2 = new MyClass();
System.out.println(myClass1.i);
System.out.println(myClass2.i);
System.out.println(myClass1.j);
System.out.println(myClass2.j);
}
}
class MyClass {
public final double i = Math.random();
public static double j = Math.random();
}
运行结果:
0.3222977275463088
0.2565532218939688
0.36856868882926397
0.36856868882926397
每次打印的两个j值都是一样的,而i的值却是不同的。从这里就可以知道final和static变量的区别了。static属于类级别的不可变,而final是对象级别的不可变。
1)final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
2)finally:异常处理语句结构的一部分,表示总是执行。
3)finalize:Object 类的一个方法,当java对象没有更多的引用指向的时候,系统会自动的由垃圾回收器来负责调用此方法进行回收前的准备工作和垃圾回收。
静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的 加载过程中,JVM只为静态变量分配一次内存空间。
实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象 的,在内存中,创建几次对象,就有几份成员变量。
静态方法和实例方法的区别主要体现在两个方面:
在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的 方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法), 而不允许访问实例成员变量和实例方法;实例方法则无此限制。
class A{
static{
System.out.print("1");
}
public A(){
System.out.print("2");
}
}
class B extends A{
static{
System.out.print("a");
}
public B(){
System.out.print("b");
}
}
public class Hello{
public static void main(String[] ars){
A ab = new B(); //执行到此处,结果: 1a2b
ab = new B(); //执行到此处,结果: 1a2b2b
}
}
输出结果为 1a2b2b;修饰符 | 当前类 | 同包 | 子类 | 其它包 |
---|---|---|---|---|
public | 是 | 是 | 是 | 是 |
protected | 是 | 是 | 是 | 否 |
默认(缺省) | 是 | 是 | 否 | 否 |
private | 是 | 否 | 否 | 否 |
类的成员不写访问修饰时默认为default。默认对于同一个包中的其他类相当于公开 (public),对于不是同一个包中的其他类相当于私有(private)。受保护(protected)对 子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。Java中,外部类的修饰符 只能是public或默认,类的成员(包括内部类)的修饰符可以是以上四种。
goto 是Java中的保留字,在目前版本的Java中没有使用。(根据James Gosling(Java之 父)编写的《The Java Programming Language》一书的附录中给出了一个Java关键字列 表,其中有goto和const,但是这两个是目前无法使用的关键字,因此有些地方将其称之为保 留字,其实保留字这个词应该有更广泛的意义,因为熟悉C语言的程序员都知道,在系统类库 中使用过的有特殊意义的单词或单词的组合都被视为保留字)
由于 Java 不支持多继承,而有可能某个类或对象要使用分别在几个类或对象里面的方法或属性,现有的单继承机制就不能满足要求。与继承相比,接口有更高的灵活性,因为接口中没有任何实现代码。当一个类实现了接口以后,该类要实现接口里面所有的方法和属性,并且接口里面的属性在默认状态下面都是public static,所有方法默认情况下是 public.一个类可以实现多个接口。
不是。Java 中的基本数据类型只有 8 个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type)外,剩下的都是引用类型(reference type)
1)String是只读的字符串,因此String引用的字符串内容是不能被改变的。
String str = "abc";
str = "bcd";
如上,第一行str 仅仅是一个引用对象,它指向一个字符串对象“abc”。第二行代码的含义是让 str 重新指向了一个新的字符串“bcd”对象,而“abc”对象并没有任何改变
2)StringBuffer/StringBuilder 表示的字符串对象可以直接进行修改。
3)StringBuilder 是 Java5 中引入的,它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方法都没有被 synchronized 修饰,因此它的效率理论上也比 StringBuffer 要高。
不一样,因为内存的分配方式不一样。String str = "i"的方式JVM会将其分配到常量池中,而 String str = new String("i")JVM会将其分配到堆内存中。
String 类是final类,不可以被继承。
补充:继承String本身就是一个错误的行为,对String类型最好的重用方式是关联关系 (Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。
两个对象,一个是静态存储区的"xyz",一个是用 new 创建在堆上的对象。
indexof();返回指定字符的的索引。
charAt();返回指定索引处的字符。
replace();字符串替换。
trim();去除字符串两端空格。
splt();字符串分割,返回分割后的字符串数组。
getBytes();返回字符串byte类型数组。
length();返回字符串长度。
toLowerCase();将字符串转换为小写字母。
toUpperCase();将字符串转换为大写字母。
substring();字符串截取。
equals();比较字符串是否相等。
数组没有 length()方法,有 length 的属性。String 有 length()方法。JavaScript 中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java混淆
String s1 = "你好"; String s2 = newString(s1.getBytes("GB2312"), "ISO-8859-1");
首先会判断要比较的两个字符串它们的引用是否相等。如果引用相等的话,直接返回 true ,不相等的话继续下面的判断,然后再判断被比较的对象是否是 String 的实例,如果不是的话直接返回 false,如果是的话,再比较两个字符串的长度是否相等,如果长度不想等的话也就没有比较的必要了;长度如果相同,会比较字符串中的每个 字符 是否相等,一旦有一个字符不相等,就会直接返回 false。
用递归实现字符串反转,代码如下所示:
public static String reverse(String originStr) {
if(originStr == null || originStr.length() <= 1)
return originStr;
return reverse(originStr.substring(1)) + originStr.charAt(0);
}
string.substring(from):相当于从from位置截取到原字符串末尾
charAt() 方法用于返回指定索引处的字符。索引范围为从 0 到 length() - 1。
public String[] split(String str, int chars){
int n = (str.length()+ chars - 1)/chars;
String ret[] = new String[n];
for(int i=0; i<n; i++){
if(i < n-1){
ret[i] = str.substring(i*chars , (i+1)*chars);
}else{
ret[i] = str.substring(i*chars);
}
}
return ret;
}
代码如下:
public String subString(String str, int subBytes) {
int bytes = 0; // 用来存储字符串的总字节数
for (int i = 0; i < str.length(); i++) {
if (bytes == subBytes) {
return str.substring(0, i);
}
char c = str.charAt(i);
if (c < 256) {
bytes += 1; // 英文字符的字节数看作 1
} else {
bytes += 2; // 中文字符的字节数看作 2
if(bytes - subBytes == 1){
return str.substring(0, i);
}
}
}
return str;
}
package test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class RandomSort {
public static void printRandomBySort() {
Random random = new Random(); // 创建随机数生成器
List list = new ArrayList(); // 生成 10 个随机数,并放在集合 list 中
for (int i = 0; i < 10; i++) {
list.add(random.nextInt(1000));
}
Collections.sort(list); // 对集合中的元素进行排序
Iterator it = list.iterator();
int count = 0;
while (it.hasNext()) { // 顺序输出排序后集合中的元素
System.out.println(++count + ": " + it.next());
}
}
public static void main(String[] args) {
printRandomBySort();
}
}
public int countWords(String file, String find) throws Exception {
int count = 0;
Reader in = new FileReader(file);
int c;
while ((c = in.read()) != -1) {
while (c == find.charAt(0)) {
for (int i = 1; i < find.length(); i++) {
c = in.read();
if (c != find.charAt(i))
break;
if (i == find.length() - 1)
count++;
}
}
}
return count;
}
Object类提供了如下几个常用方法:
Class<?> getClass():返回该对象的运行时类。
boolean equals(Object obj):判断指定对象与该对象是否相等。
int hashCode():返回该对象的hashCode值。在默认情况下,Object类的hashCode()方法根据该对象的地址来计算。但很多类都重写了Object类的hashCode()方法,不再根据地址来计算其hashCode()方法值。
String toString():返回该对象的字符串表示,当程序使用System.out.println()方法输出一个对象,或者把某个对象和字符串进行连接运算时,系统会自动调用该对象的toString()方法返回该对象的字符串表示。Object类的toString()方法返回 运行时类名@十六进制hashCode值 格式的字符串,但很多类都重写了Object类的toString()方法,用于返回可以表述该对象信息的字符串。
另外,Object类还提供了wait()、notify()、notifyAll()这几个方法,通过这几个方法可以控制线程的暂停和运行。Object类还提供了一个clone()方法,该方法用于帮助其他对象来实现“自我克隆”,所谓“自我克隆”就是得到一个当前对象的副本,而且二者之间完全隔离。由于该方法使用了protected修饰,因此它只能被子类重写或调用。
Object类提供的equals()方法默认是用==来进行比较的,也就是说只有两个对象是同一个对象时,才能返回相等的结果。而实际的业务中,我们通常的需求是,若两个不同的对象它们的内容是相同的,就认为它们相等。鉴于这种情况,Object类中equals()方法的默认实现是没有实用价值的,所以通常都要重写。
垃圾回收器回收对象前,会调用此方法,可以在此方法中做释放资源等清理操作
这两个方法用来提示 JVM 要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于 JVM 的。
利用 java.text.DataFormat 的子类(如 SimpleDateFormat 类)中的 format(Date)方法可将日期格式化。
参考如下源代码:
public class YesterdayCurrent{
public static void main(String[] args){
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
System.out.println(cal.getTime());
}
}
使用Math.random()可以生成0.1到1.0范围内的随机数字,然后通过数学方法实现生成 符合要求的随机数。
在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是 内部类 。内部类本身就 是类的一个属性,与其他属性定义方式一致。
内部类可以分为四种: 成员内部类、局部内部类、匿名内部类和静态内部类 。
一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。
可以继承其他类或实现其他接口,在 Swing 编程中常用此方式来实现事件监听和回调
Throwable 是 Java 语言中所有错误与异常的超类。
Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
Exception 程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
运行时异常 都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常 (编译异常)是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
异常需要处理的时机分为编译时异常(也叫受控异常)也叫 CheckedException 和运行时异常(也叫非受控异常)也叫 UnCheckedException。Java认为Checked异常都是可以被处理的异常,所以Java程序必须显式处理Checked异常。如果程序没有处理Checked 异常,该程序在编译时就会发生错误无法编译。这体现了Java 的设计哲学:没有完善错误处理的代码根本没有机会被执行。
对Checked异常处理方法有两种:
● 第一种:当前方法知道如何处理该异常,则用try...catch块来处理该异常。
● 第二种:当前方法不知道如何处理,则在定义该方法时声明抛出该异常。
运行时异常只有当代码在运行时才发行的异常,编译的时候不需要try…catch。Runtime如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。
Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。
一般情况下是用 try 来执行一段程序,如果出现异常,系统会抛出(throw)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理;try 用来指定一块预防所有“异常”的程序;catch 子句紧跟在 try 块后面,用来指定你想要捕捉的“异常”的类型;throw 语句用来明确地抛出一个“异常”;throws 用来标明一个成员函数可能抛出的各种“异常”;finally 为确保一段代码不管发生什么“异常”都被执行一段代码;可以在一个成员函数调用的外面写一个 try 语句,在这个成员函数内部写另一个 try 语句保护其他代码。每当遇到一个 try 语句,“异常”的框架就放到栈上面,直到所有的try 语句都完成。如果下一级的 try 语句没有对某种"异常"进行处理,栈就会展开,直到遇到有处理这种"异常"的 try 语句。
会执行,在方法返回调用者前执行。Java 允许在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try 中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,这会对程序造成很大的困扰,C#中就从语法上规定不能做这样的事。
Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。
public int getNum() {
try {
int a = 1 / 0;
return 1;
} catch (Exception e) {
return 2;
} finally {
return 3;
}
}
分析:代码走到第3行的时候遇到了一个MathException,这时第4行的代码就不会执行了,代码直接跳转到catch语句中,走到第 6 行的时候,异常机制有一个原则:如果在catch中遇到了return或者异常等能使该函数终止的话那么有finally就必须先执行完finally代码块里面的代码然后再返回值。因此代码又跳到第8行,可惜第8行是一个return语句,那么这个时候方法就结束了,因此第6行的返回结果就无法被真正返回。因此上面返回值是3。
有如下代码片断:
try{
throw new ExampleB(“b”);
}catch(ExampleA e){
System.out.printfln(“ExampleA”);
}catch(Exception e){
System.out.printfln(“Exception”);
}
输出的内容应该是:ExampleA
●java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
● java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。
● java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常。
● java.lang.ClassCastException 数据类型转换异常。
● java.lang.SQLException SQL异常,常见于操作数据库时的 SQL 语句错误。
● throw:
throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。
throw是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行throw一定是抛出了某种异常。
● throws:
throws语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
如果你的资源实现了 AutoCloseable 接口,你可以使用这个语法。大多数的 Java 标准资源都继承了这个接口。当你在 try 子句中打开资源,资源会在 try 代码块执行后或异常处理后自动关闭。
public void automaticallyCloseResource() {
File file = new File("./tmp.txt");
try (FileInputStream inputStream = new FileInputStream(file);) {
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}
public static void simpleTryCatch() {
try {
testNPE();
} catch (Exception e) {
e.printStackTrace();
}
}
使用javap来分析这段代码(需要先使用javac编译)。
//javap -c Main
public static void simpleTryCatch();
Code:
0: invokestatic #3 // Method testNPE:()V
3: goto 11
6: astore_0
7: aload_0
8: invokevirtual #5 // Method java/lang/Exception.printStackTrace:()V
11: return
Exception table:
from to target type
0 3 6 Class java/lang/Exception
异常表中包含了一个或多个异常处理者(Exception Handler)的信息,这些信息包含如下
1)from 可能发生异常的起始点
2)to可能发生异常的结束点
3)target上述from和to之前发生异常后的异常处理者的位置
4)type异常处理者处理的异常的类信息
IO流就是以流的方式进行输入输出。主要用来处理设备之间的传输,文件的上传,下载和复制。
流分输入和输出,输入流从文件中读取数据存储到进程中,输出流从进程中读取数据然后写入到目标文件。
按照流的方向:输入流(inputStream)和输出流(outputStream)
按照实现功能分:节点流(可以从或向一个特定的地方(节点)读写数据。如 FileReader)和处理流(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。)
按照处理数据的单位: 字节流和字符流。字节流继承于 InputStream 和 OutputStream, 字符流继承于Reader 和 Writer 。
1)字节流读取的时候,读到一个字节就返回一个字节;字符流读取的时候会读到一个或多个字节(这个要根据字符流中编码设置,一般中文对应的字节数是两个,在UTF-8码表中是3个字节)
2)字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
3)字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操作文件。
案例1:在写操作的过程中,没有关闭字节流操作,但是文件中也依然存在了输出的内容代码如下:
public static void main(String[] args) throws Exception {
// 第1步:使用File类找到一个文件
File f = new File("d:" + File.separator + "test.txt"); // 声明File 对象
// 第2步:通过子类实例化父类对象
OutputStream out = new FileOutputStream(f);
// 第3步:进行写操作
String str = "Hello World!!!"; // 准备一个字符串
byte b[] = str.getBytes(); // 字符串转byte数组
out.write(b); // 将内容输出
// 第4步:关闭输出流
// out.close();
}
案例2:在写操作的过程中,没有关闭字符流操作,发现文件中没有任何内容输出。代码如下:
public static void main(String[] args) throws Exception {
// 第1步:使用File类找到一个文件
File f = new File("d:" + File.separator + "test.txt");// 声明File 对象
// 第2步:通过子类实例化父类对象
Writer out = new FileWriter(f);
// 第3步:进行写操作
String str = "Hello World!!!"; // 准备一个字符串
out.write(str); // 将内容输出
out.flush();
// 第4步:关闭输出流
// out.close();
}
这是因为字符流操作时使用了缓冲区,而在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果程序没有关闭,则缓冲区中的内容是无法输出的。当然如果在不关闭字符流的情况下也可以使用Writer类中的flush()强制性的清空缓存,从而将字符流的内容全部输出。
解题思路:把字节流转成字符流就要用到适配器模式,需要用到OutputStreamWriter。它继承了Writer接口,但要创建它必须在构造函数中传入一个OutputStream的实例,OutputStreamWriter的作用也就是将OutputStream适配到Writer。它实现了Reader接口,并且持有了InputStream的引用。利用转换流OutputStreamWriter.创建一个字节流对象,将其作为参数传入转换流OutputStreamWriter中得到字符流对象.
序列化是指把对象转换为字节序列的过程,序列化后的字节流保存了对象的状态以及相关的描述信息,从而方便在网络上传输或者保存在本地文件中,达到对象状态的保存与重建的目的。
反序列化:客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
序列化的优势:一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。三是通过序列化在进程间传递对象;
(1)java.io.ObjectOutputStream:表示对象输出流;它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;
(2)java.io.ObjectInputStream:表示对象输入流;它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;
注意:只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常!
序列化和反序列化的示例
public class SerialDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
FileOutputStream fos = new FileOutputStream("object.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user1 = new User("xuliugen", "123456", "male");
oos.writeObject(user1);
oos.flush();
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("object.out");
ObjectInputStream ois = new ObjectInputStream(fis);
User user2 = (User) ois.readObject();
System.out.println(user2.getUserName()+ " " +
user2.getPassword() + " " + user2.getSex());
//反序列化的输出结果为:xuliugen 123456 male
}
}
public class User implements Serializable {
private String userName;
private String password;
private String sex;
//全参构造方法、get和set方法省略
}
1. PrintStream 类的输出功能非常强大,通常如果需要输出文本内容,都应该将输出流包装成PrintStream 后进行输出。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError 方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream
2.BufferedWriter:将文本写入字符输出流,缓冲各个字符从而提供单个字符,数组和字符串的高效写入。通过 write()方法可以将获取到的字符输出,然后通过 newLine()进行换行操作。BufferedWriter 中的字符流必须通过调用 flush 方法才能将其刷出去。并且 BufferedWriter 只能对字符流进行操作。如果要对字节流操作,则使用 BufferedInputStream
3.PrintWriter 的 println 方法自动添加换行,不会抛异常,若关心异常,需要调用 checkError方法看是否有异常发生,PrintWriter 构造方法可指定参数,实现自动刷新缓存(autoflush)。
因为明确说了是对字节流的读取,所以肯定是InputStream或者他的子类,又因为要大量读取,肯定要考虑到高效的问题,自然想到缓冲流BufferedInputStream。
原因:BufferedInputStream是InputStream的缓冲流,使用它可以防止每次读取数据时进行实际的写操作,代表着使用缓冲区。不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!并且也可以减少对磁盘的损伤。
集合就是一个放数据的容器,准确的说是放数据对象引用的容器;集合类存放的都是对象的引用,而不是对象的本身;集合类型主要有3种:set(集)、list(列表)和map(映射)。
Java1.5引入了泛型,所有的集合接口和实现都大量地使用它。泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。
Iterator接口提供遍历任何Collection的接口。我们可以从一个Collection中使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素。
数组是固定长度的;集合可变长度的。
数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。都可以存储null值,但是set不能重复所以最多只能有一个空元素。
Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。
List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。
1)Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向循环链表数据结构;
2)ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。插入末尾还好,如果是中间,则(add(int index, E element))接近O(n);LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
3)LinkedList 不支持高效的随机元素访问,而ArrayList 实现了RandmoAccess 接口,所以有随机访问功能。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。所以ArrayList随机访问快,插入慢;LinkedList随机访问慢,插入快。
4)ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
ArrayList和Vector在很多时候都很类似。
(1)两者都是基于索引的,内部由一个数组支持。
(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。
(3)ArrayList和Vector的迭代器实现都是fail-fast的。
(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。
以下是ArrayList和Vector的不同点。
(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因为有同步,不会过载。
(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
List<String> strList = new ArrayList<>();
//使用for-each循环
for(String obj : strList){
System.out.println(obj);
}
//using iterator
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String obj = it.next();
System.out.println(obj);
}
当把对象加入到HashSet中时,HashSet会先计算对象的hashCode值来判断对象加入的下标位置,同时也会与其他的对象的hashCode进行比较,如果没有相同的,就直接插入数据;如果有相同的,就进一步使用equals来进行比较对象是否相同,如果相同,就不会加入成功。
1.使用foreach循环遍历
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
2.使用foreach迭代键值对
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
for (String key : hashMap.keySet()) {
System.out.println(key);
}
for (String value : hashMap.values()) {
System.out.println(value);
}
3.使用迭代器
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
Iterator<Map.Entry<String, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> next = iterator.next();
System.out.println(next.getKey()+":"+next.getValue());
}
4.使用lambda表达式
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
hashMap.forEach((k,v)-> System.out.println(k+":"+v));
相同点:
都是实现来Map接口(hashTable还实现了Dictionary 抽象类)。
不同点:
1. 历史原因:Hashtable 是基于陈旧的 Dictionary 类的,HashMap 是 Java 1.2 引进的 Map 接口
的一个实现,HashMap把Hashtable 的contains方法去掉了,改成containsvalue 和containsKey。因为contains方法容易让人引起误解。
2. 同步性:Hashtable 的方法是 Synchronize 的,线程安全;而 HashMap 是线程不安全的,不是同步的。所以只有一个线程的时候使用hashMap效率要高。
3. 值:HashMap对象的key、value值均可为null。HahTable对象的key、value值均不可为null。
4. 容量:HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
5. HashMap扩容时是当前容量翻倍即:capacity * 2,Hashtable扩容时是容量翻倍+1 即:capacity * 2+1。
HashSet 底层就是基于 HashMap 实现的。只不过HashSet里面的HashMap所有的value都是同一个Object而已,因此HashSet也是非线程安全的。
1. HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
2. 在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。
每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过ensureCapacity(int minCapacity)方法来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。 数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。