定义
就是给类,接口方法,变量等起名字的字符序列
组成规则
可以有数字,下划线,字母,$,拼接而成 不能以数字开头 不能使用java中已有的关键字
注意事项
标识符区分大小写
A:包名全部小写
单级包:小写 列举:com多级包:小写,并用.隔开 列举:cn.oracle.myproject
B:类或者接口
一个单词:首写大写(A) 多个单词:每个单词首写大写 列举:TestDemo
C:方法或变量
一个单词:首写字母小写 多个单词 :从第二个单词开始 ,每个单词大写 列举:textDemo
D:常量
全部大写 列举:PI="3.1415926";Java中break、continue、return语句的使用区别
break:用于完全结束一个循环,跳出循环体。不管哪种循环,一旦在循环体中遇到break,系统将完全结束循环,开始执行循环之后得代码。break不仅可以结束其所在的循环,还可以结束其外层的循环。此时需要break后紧跟一个标签,这个标签用于标识一个外层循环。java中的标签就是一个紧跟着英文冒号(:)的标识符。且它必须放在循环语句之前才有用。public class BreakTest2{ public static void main(String[] args){ // 外层循环,outer作为标识符 outer: for (int i = 0 ; i < 5 ; i++ ){ // 内层循环 for (int j = 0; j < 3 ; j++ ){ System.out.println("i的值为:" + i + " j的值为:" + j); if (j == 1){ // 跳出outer标签所标识的循环。 break outer; } } } } }continue:中止本次循环,接着开始下次循环。而break则完全中止循环。public class ContinueTest{ public static void main(String[] args){// 一个简单的for循环 for (int i = 0; i < 3 ; i++ ){ System.out.println("i的值是" + i); if (i == 1){ // 忽略本次循环的剩下语句 continue; } System.out.println("continue后的输出语句"); } } }return:不是用来跳出循环的,而是用来结束一个方法,一旦在循环内执行到了一个return语句,return语句将会结束该方法,循环自然也随之结束.与continue和break不同的是,return直接结束的是整个方法,不管这个return处于多少层的循环之内。 public class ReturnTest{ public static void main(String[] args){ // 一个简单的for循环 for (int i = 0; i < 3 ; i++ ){ System.out.println("i的值是" + i); if (i == 1){ return; } System.out.println("return后的输出语句"); } } }Java面向对象
什么是对象
世界万物皆为对象,凡是能看得见摸得着的所以东西都叫对象。对象是由属性和行为组成,属性是对象所具有的特征,而行为是对象可以做的动作。
例如生活中常见的事物:汽车。汽车的品牌型号、颜色、轴距、车身大小等等都是汽车的属性;汽车向前行驶、倒车、开门等动作都叫汽车的行为。
什么是类
类:具有相同属性和行为一堆对象的集合或者叫抽象。
同学是一种类,是所有来学校上课同学的统称,集合。
程序员是一种类,是所有参与程序编写的员工的集合。
类与对象的关系
类是对象的抽象,对象的类的实例。对象的每个属性被表示为类中的一个成员变量,对象的每个行为成为类中的一个方法。
例如同学是一个类,张三就是同学类的一个实例,是一个对象。张三的姓名、年龄、成绩等都是类的成员变量,而张三说话、行走、做作业等行为就是类的方法。
代码实例:
public class Student { //定义一个名为Student的类 //定义成员变量name、age、javaScore String name; int age; double javaScore; public void say(){ //定义一个名为say的方法 int age = 10; //定义局部变量 System.out.println("该学生姓名是:"+name+"\n该学生年龄是:"+age+"\n该学生成绩是:"+javaScore); } }
上述代码中需要注意以下几点:
类名首字要大写,不能是关键字;
static尽量少用,不必要的麻烦;
成员变量又叫全局变量,可以不用赋初始值,为默认值;
局部变量必须赋初始值,另外局部变量只在方法域中起作用。
类的实例化
我们在创建了一个类之后,就需要对类进行实例化,使用类,如下面代码:
public static void main(String[] args) { Student stu = new Student(); //实例化一个Student类 stu.name = "张三"; //给每一个属性赋值 stu.age = 18; stu.javaScore = 95; stu.say(); //调用类的方法}
上面代码运行后的结果是:
该学生姓名是:张三 该学生年龄是:18 该学生成绩是:95.0
如果是存储一组学生信息,则需要用数组对Student类进行实例化,如下面代码:
public static void main(String[] args) { Scanner s = new Scanner(System.in); Student[] stu = new Student[3]; //实例化长度为3的Student类数组,存储3名同学信息 Student student = null; for (int i = 0; i < stu.length; i++) { student = new Student(); System.out.println("请输入学生姓名:"); String name = s.next(); System.out.println("请输入学生成绩:"); double score = s.nextDouble(); student.name = name; student.javaScore = score; stu[i] = student; } for (Student ss : stu){ System.out.println("学生姓名:" + ss.name + " 成绩是:" + ss.javaScore); } }
当我们输入“张三、100、李四、90、王二、80”运行结果如下:
学生姓名:张三 成绩是:100.0学生姓名:李四 成绩是:90.0学生姓名:王二 成绩是:80.0栈与堆的区别
栈空间小,堆空间大;
所有的数据都是放在栈与堆里面的;
栈运行快,对运行慢;
所有的基本类型以及引用变量本身都放在栈中;
引用所指向的对象都放在堆中;
运行方法都放在栈中,其中main方法放在最底层;
入栈的方法都要被执行,并遵行先入后出的原则
构造器是类中的一个特殊的方法,该方法在对象实例化时被调用。构造器的用途是当对象实例化时,初始化对象中的对象。构造器必须满足下面两个属性:
构造器的名称必须与类名相同;
构造器不能声明返回值,也不能返回void。
如下面的代码:
public class Pet { String name; int age; String colore; public Pet(){ System.out.println("我是构造函数"); //构造函数 } public void show(){ System.out.println("你好我是:"+name+",今年"+age+"岁了,"+colore+"色的!"); } }
构造器与方法类似,但它不是方法。每个类都必须有一个构造器,如果我们没有给类添加构造器,编译器会自动给我们添加一个构造器,默认构造器的形式参数列表是空的,如果我们给类添加了构造器,编译器就不会为类添加默认构造器。
构造器的使用
一个类可以有多个构造器,这种情况下,要调用哪个构造器就取决于new运算符所使用的实际参数。
继承(Inheritance)什么是继承
在面向对象编程中,可以通过扩展一个已有的类,并继承该类的属性和行为,来创建一个新的类,这种方式成为继承。
已有的类称为父类,而新类成为子类,父类也叫基类、超类。
使用继承不仅可以重用已有的代码,从而避免代码重复,还可以创建一个更容易维护和修改代码的程序。
使用“is a”关系判断继承
“is a”关系用于判断父类和子类的继承关系是否正确。当使用继承时,我们必须能够说子类“is a(是一个)”父类。如果这个语句是真的,那么继承关系就是对的。
Java中继承的实现在Java中,一个类使用关键字extends继承其他类。关键字extends出现在类声明时的类名后,extends后面跟着的是要继承的类的名称。例如,下面的语句用于声明Dog类是Pet类的子类:
public class Dog extends Pet{ //extends继承Pet类 String name; public Dog(){ super(); //调用父类的构造器 System.out.println("我是子类构造函数"); } }单继承与多继承
在Java中,只能有一个父类
所有类的根类Object
Java语言API中包含了一个名为Object的特殊类,他是整个Java类层次中的根类。Object类在java.lang包中,是每个Java类的父类,要么是直接的父类,要么就是间接父类。
在Java中,当引用与一个字符串连接时,JVM将隐式地调用toString()方法。
子类可以重写从父类继承的方法,从而允许子类添加或者改变父类中方法的行为,这称为方法重写,是OOP的特征之一。当子类重写父类的方法中,必须遵循如下的规则:
子类的方法的返回值的类型、方法名和形式参数列表,必须和父类是相同的;
访问修饰符必须不小于父类中的访问修饰符;
子类中重写的异常不能抛出比父类更多的异常。
如下面的代码:
public class Dog extends Pet{ String name; public Dog(){ super(); System.out.println("我是子类构造函数"); } @Override // 方法重写关键字 public void show(){ // 方法重写 System.out.println("你好我是:"+name+",今年"+age+"岁了,"+colore+"色的!"); System.out.println("`````````汪汪汪`````````\n"); } }方法重载(Overloading)
如何定义重载。
方法的重写和重载只有一个字不同,很多初学者认为这两者十分相似,其实不然。方法重载是让类以统一的方式处理不同类型数据的一种手段。调用方法时通过传递给它们的不同个数和类型的参数来决定具体使用哪个方法,这就是多态性。
所谓方法重载是指在一个类中,多个方法的方法名相同,但是参数列表不同。参数列表不同指的是参数个数、参数类型或者参数的顺序不同。方法的重载在实际应用中也会经常用到。不仅是一般的方法,构造方法也可以重载。下面通过一个实例来分析。
重载的定义和使用方法。
Class Person { { String name; int age; void print(){ System.out.println("姓名:" +name+"年龄:" +age); } void print(String a,int b){ System.out.println("姓名:" +a+"年龄:"+b); void print(String a,int b,intC){ System.out.println("姓名:"+a+"年龄:" +b+"ID号:" +c); } void print(String a,int b,doubleC){ System.out.println("姓名:"+a+"年龄:" +b+"ID号:"+c); } }public class OverLoadExampleOL {publicstaticvoidmain(String args[]) { Personpl=newPerson(); p1.nanle="李明"; p1.age=22; p1.print(); p1.print("王小早",19); p1.print("金波",18,100325); p1.print("婉宁",25,110903); } }
在上面的程序中,可以看到Person类中有多个名为 void print的方法,这就是方法的重载。执行程序,运行结果如下:
姓名:李明年龄:22 姓名:王小早年龄:l9 姓名:金波年龄:18ID号:10 00325 姓名:婉宁年龄:25ID号:110903
重载要注意以下的几点:
1.在使用重载时只能通过不同的参数列表,必须具有不同的参数列表。例如,不同的参数类型,不同的参数个数,不同的参数顺序。当然,同一方法内的几个参数类型必须不一样,例如可以是 fun(int,float),但是不能为 fun(int,int)。2.不能通过访问权限、返回类型、抛出的异常进行重载。3.方法的异常类型和数目不会对重载造成影响。.. 4.可以有不同的返回类型,只要参数列表不同就可以了。5.可以有不同的访问修饰符。6.可以抛出不同的异常。equals()方法
equals()方法比较两个对象,测试二者是否相等。
比较运算符“==”用于检测是否两个引用指向同一对象。
透切理解面向对象三大基本特性是理解面向对象五大基本原则的基础.
三大特性是:封装,继承,多态
所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。封装是面向对象的特征之一,是对象和类概念的主要特性。 简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
所谓继承是指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
五大基本原则
1.单一职责原则SRP(Single Responsibility Principle)
是指一个类的功能要单一,不能包罗万象。如同一个人一样,分配的工作不能太多,否则一天到晚虽然忙忙碌碌的,但效率却高不起来。
2.开放封闭原则OCP(Open-Close Principle)
一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。比如:一个网络模块,原来只服务端功能,而现在要加入客户端功能,
那么应当在不用修改服务端功能代码的前提下,就能够增加客户端功能的实现代码,这要求在设计之初,就应当将服务端和客户端分开,公共部分抽象出来。
3.替换原则(the Liskov Substitution Principle LSP)
子类应当可以替换父类并出现在父类能够出现的任何地方。比如:公司搞年度晚会,所有员工可以参加抽奖,那么不管是老员工还是新员工,
也不管是总部员工还是外派员工,都应当可以参加抽奖,否则这公司就不和谐了。
4.依赖原则(the Dependency Inversion Principle DIP) 具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,
这个时候,B不应当直接使用A中的具体类: 而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口:这样就达到
了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造成循环依赖。一个常见的问题就是编译A模块时需要直接包含到B模块的cpp文件,而编译B时同样要直接包含到A的cpp文件。
5.接口分离原则(the Interface Segregation Principle ISP)
模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来
this和super的区别?
(1)this代表本类对应的引用。
(2)super代表父类存储空间的标识(可以理解为父类引用可以操作父类的成员)
调用(访问)成员变量:
this.成员变量 调用本类的成员变量
super.成员变量 调用父类的成员变量
调用(访问)构造方法:
this(...) 调用本类的构造方法
super(...) 调用父类的构造方法
调用(访问)成员方法:
this.成员方法 调用本类的成员方法
super.成员方法 调用父类的成员方法
super()函数在子类构造函数中调用父类的构造函数时使用,而且必须要在构造函数的第一行
this()函数主要应用于同一类中从某个构造函数调用另一个重载版的构造函数。
this()只能用在构造函数中,并且也只能在第一行。所以在同一个构造函数中this()和super()不能同时出现。
接口和抽象类有什么区别你选择使用接口和抽象类的依据是什么?
接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。
`抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。`
比如,男人,女人,这两个类(如果是类的话……),他们的抽象类是人。说明,他们都是人。
人可以吃东西,狗也可以吃东西,你可以把“吃东西”定义成一个接口,然后让这些类去实现它.
所以,在高级语言上,一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。
第一点. 接口是抽象类的变体,接口中所有的方法都是抽象的。而抽象类是声明方法的存在而不去实现它的类。 第二点. 接口可以多继承,抽象类不行 第三点. 接口定义方法,不能实现,而抽象类可以实现部分方法。 第四点. 接口中基本数据类型为static 而抽类象不是的。
当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的
所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度的。
有效处理java异常的三个原则Java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮、易于调试。异常之所以是一种强大的调试手段,在于其回答了以下三个问题:
什么出了错? 在哪出的错? 为什么出错?
在有效使用异常的情况下,异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出,如果你的异常没有回答以上全部问题,那么可能你没有很好地使用它们。有三个原则可以帮助你在调试过程中最大限度地使用好异常,这三个原则是:
具体明确 提早抛出 延迟捕获01.异常
1.异常
:是对问题的描述,将问题进行对象的封装。
2.异常体系:
Throwable |---Error |---Exception |---RuntimeException
3.异常体系的特点:
异常体系中的所有类以及建立的对象都具备可抛性。也就是说可以被throw和throws关键字所操作,只有异常体系具备这个特点。
4.throw和throws的用法:
throw定义在函数内,用于抛出异常对象。 throws定义在函数上,用于抛出异常类,可以抛出多个用逗号隔开。
5.当函数内有throw抛出异常对象 ,
并未进行try处理,必须要在函数上声明,否则编译失败。 注意:RuntimeException除外,也就是说,函数内如果抛出的RuntimeException异常, 函数上可以不用声明。如果函数声明了异常,调用者需要进行处理,处理方法可以throws 也可以try.
6.异常有两种:
(1)编译时被检测异常: <1>该异常在编译时,如果没有处理,那么编译失败。 <2>该异常被标识,代表可以被处理。 (2)运行时异常(编译时不检测) <1>在编译时,不需要处理,编译器不检查。 <2>该异常的发生,建议不处理,让程序停止,需要对代码进行修正。
7.异常处理语句
try { 需要被检测的代码; } catch() { 处理异常的代码; } finally { 一定会执行的代码; }
8.三和结合格式:(try-catch-finally)
注: finally中定义的通常是关闭资源代码,因为资源必须释放。 finally只有一种情况不会被执行,就是当执行到System.exit(0)时。
9.自定义异常:
(1)定义灯继承Exception或RuntimeException
<1>为了让该自定义类具备可抛性。
<2>让该类具备操作异常的共性方法。
(2)当要定义自定义异常的信息时,可以使用父类已经定义好的功能,将异常信息传递给父类的构造函数。
class MyException extends Exception
{
MyException(String message)
{
super(message);
}
}
自定义异常:按照java的面向对象思想,将程序中出现的特有问题进行封装。
10异常的好处:
(1)将问题进行封装。 (2)将正常流程代码和问题处理代码相分离,方便于阅读。
11.异常处理原则:
(1)处理方式有两种:try或throws. (2)调用到抛出异常的功能时,抛出几个就处理几个,一个try可对应多个catch. (3)多个catch时,父类的catch放到最下面。 (4)catch内,需要定义针对性的处理方式,不要简单的定义printStackTrace,输出语句,也不要不写。 (5)当捕捉到的异常,本功能处理不了时,可以继续在catch中抛出。 try { throw new AException(); } catch(AException e) { throw e; } (6)如果该异常处理不了,但并不属于该功能出现的异常,可以将异常转换后,再抛出和该功能相关的异常。 try { throw new AException(); } catch(AException e) { //在抛出B之前,对A先进行处理。 throw new BException(); } 注:或者异常可以处理,当需要将异常产生后和本功能相关的问题提供出去,当调用者知道,并处理 也可以将捕获异常处理后,转换新的异常。
12.异常的注意事项:
在子父类覆盖时: (1)子类抛出的异常必须是父类的异常的子类或子集。 (2)如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能try不能抛。02.包(package)
1.包:
包也是一种封装形式,可对类文件进行分类管理,给类提供多层命名空间。 写在程序文件的第一行,类名的全称是:包名.类名
2.包的好处:
包的出现可以让类文件和java的源文件相分离,可以很方便地只将类文件提供给别人使用。
3.包与包之间的访问:
(1)一个包中的类要被访问,必须要有足够大的权限,所以被访问的类要被public修饰。 (2)类公有后,被访问的成员也要公有才可以被访问。 (3)包与包之间进行访问,被访问的包中的类以及类中的成员,需要public修饰。 (4)不同包中的子类还可直接访问父类中被protected权限修饰的成员。 (5)包与包之间可以使用的权限只有两种:public,protected.
4.java中四种权限
public protected default private
image
5.import
(1)为了简化类名的书写,使用一个关键字import,import导入的是包中的类。 (2)import packb.*;为导入包中所有的类,尽量不要写通配符*,需要用到哪个类就导入哪个类, 都导进来比较占内存。 (3)建立定义包名不要重复,可以使用url来完成定义,因为url是唯一的。 如:package cn.itcast.test (4)包和子包中有重名类时,创建对象必须加包名。 如:packb包和子包haha中都有DemoC,创建包中DemoC对象代码为: packb.DemoC c = new packb.DemoC();
6.jar包
(1)java中的jar包是通过工具jar.exe完成的。 (2)java的压缩包方便项目的携带,方便于使用,只要在classpath设置jar路径即可。 (3)数据库驱动,SSH框架等都是以jar包体现的。JAVA多线程实现的四种方式
Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。
其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。
1、继承Thread类创建线程
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:
public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } } MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); myThread1.start(); myThread2.start();
2、实现Runnable接口创建线程
如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口,如下:
public class MyThread extends OtherClass implements Runnable { public void run() { System.out.println("MyThread.run()"); } }
为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:
MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start();
事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:
public void run() { if (target != null) { target.run(); } }
3、实现Callable接口通过FutureTask包装器来创建Thread线程
Callable接口(也只有一个方法)定义如下:
public interface Callable<V> { V call() throws Exception; } public class SomeCallable<V> extends OtherClass implements Callable<V> {@Overridepublic V call() throws Exception {// TODO Auto-generated method stubreturn null; } } Callable<V> oneCallable = new SomeCallable<V>(); //由Callable<Integer>创建一个FutureTask<Integer>对象: FutureTask<V> oneTask = new FutureTask<V>(oneCallable); //注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。 //由FutureTask<Integer>创建一个Thread对象: Thread oneThread = new Thread(oneTask); oneThread.start(); //至此,一个线程就创建完成了。
4、使用ExecutorService、Callable、Future实现有返回结果的线程
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:
import java.util.concurrent.*; import java.util.Date; import java.util.List; import java.util.ArrayList; /** * 有返回值的线程 */ @SuppressWarnings("unchecked") public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序开始运行----"); Date date1 = new Date(); int taskSize = 5; // 创建一个线程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 创建多个有返回值的任务 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 执行任务并获取Future对象 Future f = pool.submit(c); // System.out.println(">>>" + f.get().toString()); list.add(f); } // 关闭线程池 pool.shutdown(); // 获取所有并发任务的运行结果 for (Future f : list) { // 从Future对象上获取任务的返回值,并输出到控制台 System.out.println(">>>" + f.get().toString()); } Date date2 = new Date(); System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>>" + taskNum + "任务启动"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任务终止"); return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】"; } }
代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
线程的五大状态线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态.
image
1.新建状态(New): 当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码 2.就绪状态(Runnable) 一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。 处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。 3.运行状态(Running) 当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法. 4. 阻塞状态(Blocked) 线程运行过程中,可能由于各种原因进入阻塞状态: 1>线程通过调用sleep方法进入睡眠状态; 2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者; 3>线程试图得到一个锁,而该锁正被其他线程持有; 4>线程在等待某个触发条件; ...... 所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。 5. 死亡状态(Dead) 有两个原因会导致线程死亡: 1) run方法正常退出而自然死亡, 2) 一个未捕获的异常终止了run方法而使线程猝死。 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.线程的同步与死锁
1、同步问题
所谓的同步问题指的是多个线程操作同一资源时所带来的信息的安全性问题,例如,下面模拟一个简单的卖票程序,要求有5个线程,卖6张票。
class MyThread1 implements Runnable{ //线程的主体类 private int ticket = 6; @Override public void run() { //线程主方法 for(int x =0; x < 10; x++){ if(this.ticket > 0){ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"买票,ticket=" + this.ticket--); } } } }public class TestDemo { public static void main(String[] args){ MyThread1 mt = new MyThread1(); new Thread(mt,"票贩子A").start(); new Thread(mt,"票贩子B").start(); new Thread(mt,"票贩子C").start(); new Thread(mt,"票贩子D").start(); new Thread(mt,"票贩子E").start(); } } 运行结果: 票贩子C买票,ticket=6票贩子D买票,ticket=2票贩子B买票,ticket=3票贩子E买票,ticket=4票贩子A买票,ticket=5票贩子C买票,ticket=1票贩子D买票,ticket=0票贩子B买票,ticket=-1票贩子E买票,ticket=-2票贩子A买票,ticket=-3
这个时候发现操作的数据之中出现了负数,这个就可以理解为不同步问题。
从运行结果发现,程序中加入了延时操作,所以在运行的最后出现了负数的情况,那么为什么现在会产生这样的情况呢?
从上面的操作代码可以发现对于票数的操作如下:
1、判断票数是否大于0,大于0则表示还有票可以卖。
2、如果票数大于0,则卖票出去。
但是,在上面的操作代码中,在第一步和第二步之间加入了延时操作,那么一个线程就有可能在还没有对票数进行减操作之前,其他进程就已经将票数减少了,这样一来就会出现票数为负的情况。
如果说现在只剩下最后一张票了,一个线程判断条件满足,但是在它还没修改票数之后,其他线程也同时通过了if判断,所以最终修改票数的时候就变成了负数。
问题的解决
如果想解决这样的问题,就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只能有一个线程进行(锁住),其他线程要等待此线程完成之后才可以继续执行。
如果现在要想增加这个锁,在程序之中就可以通过两种方式完成:一种是同步代码块,另外一种就是同步方法。
实现一:同步代码块,使用synchronized关键字定义的代码块就称为同步代码块,但是在进行同步的操作之中必须设置一个要同步的对象,而这个对象应该理解为当前对象:this。
class MyThread1 implements Runnable{ //线程的主体类 private int ticket = 6; @Override public void run() { //线程主方法 for(int x =0; x < 10; x++){ synchronized (this) { if(this.ticket > 0){ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"买票,ticket=" + this.ticket--); } } } } }public class TestDemo { public static void main(String[] args){ MyThread1 mt = new MyThread1(); new Thread(mt,"票贩子A").start(); new Thread(mt,"票贩子B").start(); new Thread(mt,"票贩子C").start(); new Thread(mt,"票贩子D").start(); new Thread(mt,"票贩子E").start(); } } 运行结果: 票贩子A买票,ticket=6票贩子E买票,ticket=5票贩子E买票,ticket=4票贩子C买票,ticket=3票贩子C买票,ticket=2票贩子D买票,ticket=1
方式二,同步方法
class MyThread1 implements Runnable{ //线程的主体类 private int ticket = 6; @Override public void run() { //线程主方法 for(int x =0; x < 10; x++){ this.sale(); } } public synchronized void sale(){ if(this.ticket > 0){ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"买票,ticket=" + this.ticket--); } } }public class TestDemo { public static void main(String[] args){ MyThread1 mt = new MyThread1(); new Thread(mt,"票贩子A").start(); new Thread(mt,"票贩子B").start(); new Thread(mt,"票贩子C").start(); new Thread(mt,"票贩子D").start(); new Thread(mt,"票贩子E").start(); } } 运行结果: 票贩子A买票,ticket=6票贩子E买票,ticket=5票贩子E买票,ticket=4票贩子E买票,ticket=3票贩子D买票,ticket=2票贩子C买票,ticket=1
但是在此处需要说明的一个问题:加入同步之后明显比不加入同步慢许多,所以同步的代码性能会很低,但是数据的安全性会高。
2、死锁
同步就是指一个线程要等待另外一个线程执行完毕才会继续执行的一种操作形式,但是如果在一个操作之中都是在互相等着的话,那么就会出现死锁问题。
示例:
下面模拟一个死锁程序的样式。
class YuShi {public synchronized void say(FuXie f) { System.out.println("玉史:给我30亿欧圆,放了你儿子。"); f.get() ; }public synchronized void get() { System.out.println("玉史终于得到了赎金,放了儿子,为了下次继续绑架。"); } }class FuXie { public synchronized void say(YuShi y) { System.out.println("付谢:放了我儿子,我给你30亿欧圆,不见人不给钱。") ; y.get() ; }public synchronized void get() { System.out.println("付谢救回了自己的儿子,于是开始哭那30亿。"); } }public class DeadLock implements Runnable { static YuShi ys = new YuShi() ; static FuXie fx = new FuXie() ;public static void main(String[] args) { new DeadLock() ; }public DeadLock() { new Thread(this).start() ; ys.say(fx) ; }@Overridepublic void run() { fx.say(ys) ; } }
死锁是在日后多线程程序开发之中经常会遇见的问题,而以上的代码并没有任何的实际意义,大概可以理解死锁的操作形式就可以了,不用去研究程序。
面试题:
请问多个线程操作同一资源的时候要考虑到那些,会带来那些问题?
答:多个线程访问同一资源的时候一定要考虑到同步的问题,但是过多的同步会带来死锁。
Java集合框架篇Collection—List&SetCollection
集合类特点
一、只用于存储对象的引用(地址) 二、存储长度可变 三、存储不同类型的对象
在生活中我们有着各种各样的容器,有玻璃的,有陶瓷的,有带把手的,有带隔板的,这些容器有着共同的特性就是一个容器,同时也有着不同的功能差异。在Java中也是如此有着各种各样的集合,我们可以把它这个大家族称之为集合框架。每一个容器对数据的存储方式都有不同,这些存储方式我们称之为:数据结构。常用集合基本图如下:
基本集合框架图
image
public interface Collection<E> extends Iterable<E>
Collection 层次结构 中的根接口。
Collection 表示一组对象,这些对象也称为 collection 的元素。
一些 collection 允许有重复的元素,而另一些则不允许。
一些 collection 是有序的,而另一些则是无序的。
JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set 和 List)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
—— jdk api
List特点:
元素都是有序的 元素可重复 该集合体系有索引 持有特有的ListIterator迭代器 所以凡是操作角标的集合方法都是List特有的方法。
public interface List<E> extends Collection<E>
有序的 collection(也称为序列)。
此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
与 set 不同,列表通常允许重复的元素。
—— jdk api
该列表特点:
底层数据结构为数组结构。因此查改快,增删较慢
非同步操作
ArrayList()初始容量为10的空列表,每次new +50%增长,数据Copy
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, SerializableList
接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)
—— jdk api
ArrayList 基本用法
import java.util.*;class ListDemo{public static void main(String[] args){ ArrayList a1 = new ArrayList(); a1.add("01"); a1.add("02"); a1.add("03"); print(a1); // for (Iterator it = a1.iterator();it.hasNext() ; ) { // Object obj = it.next(); // if (obj.equals("02")) { // // a1.add("04") // //操作元素的方式不能同时用集合对象和迭代器 //会发生并发修改异常 // //ConcurrentModificationException // it.remove(); // } // } //List集合特有的迭代器ListIterator是Iterator的子接口 //该接口只能通过List的集合的ListIterator获取 ListIterator li = a1.listIterator(); for (;li.hasNext() ; ) { //迭代次数是固定的并不会因为在迭代过程中添加或删除元素就改变 // 变化的是集合而不是迭代器 if (li.next().equals("03")) { li.set("003"); li.add("04"); //注意:如果顺序颠倒会报异常 } print(1); } print(a1); //[01, 02, 03] //[01, 02, 003, 04] print(li.hasNext()+" "+li.hasPrevious()); //false true}public static void method(){ ArrayList a1 = new ArrayList(); a1.add("01"); a1.add(02); a1.add(03); print(a1+""+a1.size()); //[01, 2, 3]3 a1.add(1,"08"); print(a1); //[01, 08, 2, 3] a1.remove(2); print(a1); //[01, 08, 3] a1.set(2,"set008"); print(a1); //[01, 08, set008] print(a1.get(1)); //08 for (int x=0; x<a1.size(); x++) { print("for-get:"+a1.get(x)); } for (Iterator it = a1.iterator();it.hasNext() ; ) { print("Iterator:"+it.next()); } /* for-get:01 for-get:08 for-get:set008 Iterator:01 Iterator:08 Iterator:set008 */ print("index="+a1.indexOf("08")); //index=1 List sub = a1.subList(0,2); print("sub"+sub); //sub[01, 08]}public static void print(Object obj){ System.out.println(obj); }
}
ArrayList 基本元素存储去重实现
import java.util.*;//ArrayList 去重//在迭代时循环中 next 调用一次,就要hasNext判断一次class ArrayListTest{public static void main(String[] args){ ArrayList al = new ArrayList(); al.add("001"); al.add("002"); al.add("002"); al.add("001"); print(al); al = singleElement(al); print(al); }public static void print(Object obj){ System.out.println(obj); }public static ArrayList singleElement(ArrayList al){ ArrayList newAl = new ArrayList(); for (Iterator it = al.iterator();it.hasNext() ; ) { Object obj = it.next(); if (!newAl.contains(obj)) { //contains的底层原理就是用equals比较 newAl.add(obj); } } return newAl; }
}
ArrayList 对象存储去重实现
import java.util.*;/* 当ArrayList中是对象时,需要对Object的equals进行规则重写 因为在List中数据的比较操作都是通过equals来判断的 */class ArrayListTest2{public static void main(String[] args){ ArrayList al = new ArrayList(); al.add(new Person("wanger1",32)); al.add(new Person("wanger2",52)); al.add(new Person("wanger3",33)); al.add(new Person("wanger4",31)); al.add(new Person("wanger4",31)); al = singleElement(al); for (Iterator it = al.iterator();it.hasNext() ; ) { Person p = (Person)it.next(); print(p.getName()+":"+p.getAge()); } }public static void print(Object obj){ System.out.println(obj); }public static ArrayList singleElement(ArrayList al){ ArrayList newAl = new ArrayList(); for (Iterator it = al.iterator();it.hasNext() ; ) { Object obj = it.next(); if (!newAl.contains(obj)) { newAl.add(obj); } } return newAl; } }class Person{private String name;private int age; Person(String name,int age){ this.name = name; this.age = age; }public String getName(){ return name; }public int getAge(){ return age; }public boolean equals(Object obj){ if(!(obj instanceof Person)) return false; //instanceof进行类型检查规则是: //你属于该类吗?或者你属于该类的派生类吗? Person p = (Person)obj; System.out.println(this.name+"..."+p.name); return this.name.equals(p.name) && this.age == p.age; } }
LinkedList
该列表特点: 底层数据结构为链表结构。因此查改慢,增删快 也是非同步的 LinkedList() 构造的是一个空列表
为了更好理解链表示意图如下:
LinkedList操作示意图
image
由此可以看出LinkedList的增删都对列表的改动不大,而查改则需要诶个数据进行询问就很麻烦。
LinkedList 基础用法
import java.util.*;/* addFirst(); addLast(); getFirst(); getLast(); removeFirst(); removeLast(); 有返回值并删除当前返回值,没有元素会出现NoSuchElementException JDK1.6替代方法 offerFiest(); offerLast(); peekFirst(); peeklast(); pollFirst(); pollLast();
以上 获取元素,没有时会返回null
*/class LinkedListDemo{public static void main(String[] args){ LinkedList link = new LinkedList(); link.addFirst("001"); link.addFirst("002"); link.addFirst("003"); link.addFirst("004"); print(link); //[004, 003, 002, 001] print(link.getLast()); //001 print(link.getFirst()); //004 print(link.removeFirst()); //001}public static void print(Object obj){ System.out.println(obj); } }
用LinkedList 实现队列或者堆栈
import java.util.*;//堆栈 : 先进后出 First In Last Out FILO//队列 : 先进先出 First In First Out FIFOclass LinkedListTest{public static void main(String[] args){ Queue q = new Queue(); q.myAdd("001"); q.myAdd("002"); q.myAdd("003"); while(!q.isNull()){ print(q.myGet()); } }public static void print(Object obj){ System.out.println(obj); } }class Queue{private LinkedList link; Queue(){ link = new LinkedList(); }public void myAdd(Object obj){ link.addFirst(obj); }public Object myGet(){ return link.removeLast(); }public boolean isNull(){ return link.isEmpty(); }
}
总结在List中还有一种容器结构叫做Vector,功能与ArrayList是一样的,但是它是同步操作,特有取出方式是枚举(与Iterator功能一致但名字太长被抛弃),每次增长是100%。
当我们判断不定用ArrayList还是LinkedList的时候,我们可以看看数据是否庞大并且需要大量的增删,若是就用LinkedList,否就用ArrayList吧,因为平时一般大量的都是查看。
Set
Set特点:
元素排列是无序的 不可重复 无索引
public interface Set<E>extends Collection<E>
一个不包含重复元素的 collection。
更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。
正如其名称所暗示的,此接口模仿了数学上的 set 抽象。
—— jdk api
HashSet
HashSet特点:
底层数据结构是Hash表(存放一堆Hash值的表) 保证元素唯一性原理:判断元素hashCode值是否相同,若相同再用equals比较是否为同一个对象 非同步
我们知道每一个存储在集合中的对象都会有一个来至Object的hashCode()方法,这个方法就为我们生成Hash值,所以有时当我们每次用 对象 OR对象.toString() 打印时就会出现类似 Person@xxx @前面的是该对象实例化的类名 后面部分是hashCode值,注:hashCode值与内存地址值可能有关系但不是必然的,只是一种约定,并不强制。
hashCode
public int hashCode()
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
hashCode 的常规协定是:
一、在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 二、如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。 三、如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
toString
public String toString()返回该对象的字符串表示。
通常,toString方法会返回一个“以文本方式表示”此对象的字符串。
结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。 Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at标记符“@”和此对象哈希码的无符号十六进制表示组成。
换句话说,该方法返回一个字符串,它的值等于:
getClass().getName() + '@' + Integer.toHexString(hashCode())
—— jdk api Object
HashSet 中实现存储自定义类去重
import java.util.*;class HashSetTest{public static void main(String[] args){ HashSet hs = new HashSet(); hs.add(new Person("w1",12)); hs.add(new Person("w2",13)); hs.add(new Person("w3",14)); hs.add(new Person("w1",12)); for (Iterator it = hs.iterator();it.hasNext() ; ) { Person p = (Person)it.next(); print(p.getName()+":"+p.getAge()); } }public static void print(Object obj){ System.out.println(obj); } }class Person{private String name;private int age; Person(String name,int age){ this.name = name; this.age = age; }public int hashCode(){ System.out.println(this.name+"...hashCode..."); return this.name.hashCode() + this.age*37; //对象间比较必须重写hashCode方法。默认每个new对象是必然不同的 //*37是为了减小偶然性,注意不要越界了。}public String getName(){ return name; }public int getAge(){ return age; }public boolean equals(Object obj){ if(!(obj instanceof Person)) return false; Person p = (Person)obj; System.out.println(this.name+"::"+p.name); return this.name.equals(p.name) && this.age == p.age; } }
TreeSet
TreeSet特点:
可以对Set集合中元素进行排序(默认ASCII码大小排序又称自然排序)
底层用了一个平衡二叉树(红黑树)的数据结构
非同步
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable
基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
TreeSet底层图解
image
TreeSet 在查找元素的时候就会从根节点开始比较,就会减少一半边的工作量,但是当元素多了的时候,就会在根节点中取折中值再比较。
既然说到了比较那么就会引入它的比较原理了。
TreeSet
public TreeSet()构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
插入该 set 的所有元素都必须实现 Comparable 接口。
另外,所有这些元素都必须是可互相比较的:对于 set 中的任意两个元素 e1 和 e2,执行 e1.compareTo(e2) 都不得抛出 ClassCastException。如果用户试图将违反此约束的元素添加到 set(例如,用户试图将字符串元素添加到其元素为整数的set中,则add调用将抛出ClassCastException(类型转换异常)。
从它的构造方法我们可以得知很重要的信息就是添加的数据间必须具备可比较性
public interface Comparable<T>此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
int compareTo(T o)比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
所以当我们需要用TreeSet中添加自定义对象时,一定要记着让对象具备比较性。
import java.util.*;class TreeSetTest{public static void main(String[] args){ TreeSet hs = new TreeSet(new myCompare()); // TreeSet hs = new TreeSet(); hs.add(new Student("w1",15)); hs.add(new Student("w2",13)); hs.add(new Student("w4",14)); hs.add(new Student("w3",14)); hs.add(new Student("w3",14)); for (Iterator it = hs.iterator();it.hasNext() ; ) { Student p = (Student)it.next(); print(p.getName()+":"+p.getAge()); } }public static void print(Object obj){ System.out.println(obj); } }class Student implements Comparable{private String name;private int age; Student(String name,int age){ this.name = name; this.age = age; }public String getName(){ return name; }public int getAge(){ return age; }public int compareTo(Object obj){ if(! (obj instanceof Student)) throw new RuntimeException("Not Student Object"); Student s = (Student)obj; if (this.age == s.age) { return this.name.compareTo(s.name); } return this.age - s.age; //返回值为0,是会认为相同对象,就不会存进去 //String类本身就实现了Comparable} }class myCompare implements Comparator{public int compare(Object o1,Object o2){ if(!(o1 instanceof Student) || !(o2 instanceof Student)) throw new RuntimeException("Not Student Object"); Student s1 = (Student)o1; Student s2 = (Student)o2; int num = s1.getName().compareTo(s2.getName()); if(num == 0){ return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge())); } return num; } }
在上述代码示例中,我同时使用了另一种方法让元素具备了比较性,那就是在实例化TreeSet()类时传入一个构造器。
public TreeSet(Comparator<? super E> comparator)
构造一个新的,空的树集,根据指定的比较器进行排序。
插入到集合中的所有元素必须由指定的比较器相互比较 : comparator.compare(e1, e2)不能为ClassCastException中的任何元素e1和e2 。
如果用户尝试向该集合添加一个违反此约束的元素,则add调用将抛出ClassCastException 。
参数 comparator - 将用于对该集合进行排序的比较器。
Comparator让集合自身具备比较性,在集合初始化时,就有了比较方式。
可能有人会问这个比较器Comparator和另一个实现Comparable接口,应该在哪里用? 解答:当我们在项目中写代码时,若是你不能改其他人的在自定义类中的已经实现了Comparable接口覆盖compareTo方法,但是你却需要用其他的比较方式,这个时候就可以写一个比较器Comparator覆盖compare方法,当你使用比较器时,它会具有优先性。其次当你使用的元素不具备比较性或者不具备所需要的比较性的时候,这个时候只需使用比较器让容器自身具备比较性。/* 练习:按照字符串长度排序 字符串本身具备比较性,但是它的比较方式不是所需要的 这时就只能使用比较器 */import java.util.*;class TreeSetTest2{ public static void main(String[] args){ TreeSet hs = new TreeSet(new strLenComparator()); hs.add("w1"); hs.add("w2"); hs.add("w333"); hs.add("w222"); hs.add("w333"); for (Iterator it = hs.iterator();it.hasNext() ; ) { print((String)it.next()); } } public static void print(Object obj){ System.out.println(obj); } }class strLenComparator implements Comparator{ public int compare(Object o1,Object o2){ if(!(o1 instanceof String) || !(o2 instanceof String)) throw new RuntimeException("Not String Object"); String s1 = (String)o1; String s2 = (String)o2; int num = new Integer(s1.length()).compareTo(s2.length()); if(num == 0) return s1.compareTo(s2); return num; } }
总结
HashSet 允许放入一个nullTreeSet 不允许放入nullHashSet 无序 TreeSet 有序 Hash是用空间换时间 Tree是为了有序 HashSet 底层是 HashMap TreeSet 底层是TreeMapMap集合的四种遍历方式
1import java.util.HashMap; 2 import java.util.Iterator; 3 import java.util.Map; 4 5 public class TestMap { 6 public static void main(String[] args) { 7 Map<Integer, String> map = new HashMap<Integer, String>(); 8 map.put(1, "a"); 9 map.put(2, "b");10 map.put(3, "ab");11 map.put(4, "ab");12 map.put(4, "ab");// 和上面相同 , 会自己筛选13 System.out.println(map.size());14 // 第一种:15 /* 16 * Set<Integer> set = map.keySet(); //得到所有key的集合 17 * 18 * for (Integer in : set) { String str = map.get(in); 19 * System.out.println(in + " " + str); } 20 */21 System.out.println("第一种:通过Map.keySet遍历key和value:");22 for (Integer in : map.keySet()) {23 //map.keySet()返回的是所有key的值24 String str = map.get(in);//得到每个key多对用value的值25 System.out.println(in + " " + str);26 }27 // 第二种:28 System.out.println("第二种:通过Map.entrySet使用iterator遍历key和value:");29 Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();30 while (it.hasNext()) {31 Map.Entry<Integer, String> entry = it.next();32 System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());33 }34 // 第三种:推荐,尤其是容量大时35 System.out.println("第三种:通过Map.entrySet遍历key和value");36 for (Map.Entry<Integer, String> entry : map.entrySet()) {37 //Map.entry<Integer,String> 映射项(键-值对) 有几个方法:用上面的名字entry38 //entry.getKey() ;entry.getValue(); entry.setValue();39 //map.entrySet() 返回此映射中包含的映射关系的 Set视图。40 System.out.println("key= " + entry.getKey() + " and value= "41 + entry.getValue());42 }43 // 第四种:44 System.out.println("第四种:通过Map.values()遍历所有的value,但不能遍历key");45 for (String v : map.values()) {46 System.out.println("value= " + v);47 }48 }49 }IO流上:概述、字符流、缓冲区(java基础
一、IO流概述
概述:
IO流简单来说就是Input和Output流,IO流主要是用来处理设备之间的数据传输,Java对于数据的操作都是通过流实现,而java用于操作流的对象都在IO包中。
分类:
按操作数据分为:字节流和字符流。 如:Reader和InpurStream 按流向分:输入流和输出流。如:InputStream和OutputStream
IO流常用的基类:
* InputStream , OutputStream
字符流的抽象基类:
* Reader ,Writer
由上面四个类派生的子类名称都是以其父类名作为子类的后缀:
如:FileReader和FileInputStream
二、字符流
字符流简介:
字符流中的对象融合了编码表,也就是系统默认的编码表。我们的系统一般都是GBK编码。
字符流只用来处理文本数据,字节流用来处理媒体数据。
数据最常见的表现方式是文件,字符流用于操作文件的子类一般是FileReader和FileWriter。
2.字符流读写:
注意事项:
* 写入文件后必须要用flush()刷新。 * 用完流后记得要关闭流 * 使用流对象要抛出IO异常
定义文件路径时,可以用“/”或者“\”。
在创建一个文件时,如果目录下有同名文件将被覆盖。
在读取文件时,必须保证该文件已存在,否则出异常
示例1:在硬盘上创建一个文件,并写入一些文字数据
[java] view plain copyclass FireWriterDemo { public static void main(String[] args) throws IOException { //需要对IO异常进行处理 //创建一个FileWriter对象,该对象一被初始化就必须要明确被操作的文件。 //而且该文件会被创建到指定目录下。如果该目录有同名文件,那么该文件将被覆盖。 FileWriter fw = new FileWriter("F:\\1.txt");//目的是明确数据要存放的目的地。 //调用write的方法将字符串写到流中 fw.write("hello world!"); //刷新流对象缓冲中的数据,将数据刷到目的地中 fw.flush(); //关闭流资源,但是关闭之前会刷新一次内部缓冲中的数据。当我们结束输入时候,必须close(); fw.write("first_test"); fw.close(); //flush和close的区别:flush刷新后可以继续输入,close刷新后不能继续输入。 } }
示例2:FileReader的reade()方法.
要求:用单个字符和字符数组进行分别读取
[java] view plain copyclass FileReaderDemo { public static void main(String[] args) { characters(); } /*****************字符数组进行读取*********************/ private static void characters() { try { FileReader fr = new FileReader("Demo.txt"); char [] buf = new char[6]; //将Denmo中的文件读取到buf数组中。 int num = 0; while((num = fr.read(buf))!=-1) { //String(char[] value , int offest,int count) 分配一个新的String,包含从offest开始的count个字符 sop(new String(buf,0,num)); } sop('\n'); fr.close(); } catch (IOException e) { sop(e.toString()); } } /*****************单个字母读取*************************/ private static void singleReader() { try { //创建一个文件读取流对象,和指定名称的文件关联。 //要保证文件已经存在,否则会发生异常:FileNotFoundException FileReader fr = new FileReader("Demo.txt"); //如何调用读取流对象的read方法? //read()方法,一次读取一个字符,并且自动往下读。如果到达末尾则返回-1 int ch = 0; while ((ch=fr.read())!=-1) { sop((char)ch); } sop('\n'); fr.close(); /*int ch = fr.read(); sop("ch=" + (char)ch); int ch2 = fr.read(); sop("ch2=" + (char)ch2); //使用结束注意关闭流 fr.close(); */ } catch (IOException e) { sop(e.toString()); } } /**********************Println************************/ private static void sop(Object obj) { System.out.print(obj); } }
示例3:对已有文件的数据进行续写
[java] view plain copyimport java.io.*; class FileWriterDemo3 { public static void main(String[] args) { try { //传递一个参数,代表不覆盖已有的数据。并在已有数据的末尾进行数据续写 FileWriter fw = new FileWriter("F:\\java_Demo\\day9_24\\demo.txt",true); fw.write(" is charactor table?"); fw.close(); } catch (IOException e) { sop(e.toString()); } } /**********************Println************************/ private static void sop(Object obj) { System.out.println(obj); } }
练习:
将F盘的一个文件复制到E盘。
思考:其实就是将F盘下的文件数据存储到D盘的一个文件中。
步骤:
1.在D盘创建一个文件,存储F盘中文件的数据。
2.定义读取流和F:盘文件关联。
3.通过不断读写完成数据存储。
4.关闭资源。
源码:
[java] view plain copyimport java.io.*; import java.util.Scanner; class CopyText { public static void main(String[] args) throws IOException { sop("请输入要拷贝的文件的路径:"); Scanner in = new Scanner(System.in); String source = in.next(); sop("请输入需要拷贝到那个位置的路径以及生成的文件名:"); String destination = in.next(); in.close(); CopyTextDemo(source,destination); } /*****************文件Copy*********************/ private static void CopyTextDemo(String source,String destination) { try { FileWriter fw = new FileWriter(destination); FileReader fr = new FileReader(source); char [] buf = new char[1024]; //将Denmo中的文件读取到buf数组中。 int num = 0; while((num = fr.read(buf))!=-1) { //String(char[] value , int offest,int count) 分配一个新的String,包含从offest开始的count个字符 fw.write(new String(buf,0,num)); } fr.close(); fw.close(); } catch (IOException e) { sop(e.toString()); } } /**********************Println************************/ private static void sop(Object obj) { System.out.println(obj); }
}
三、缓冲区
1. 字符流的缓冲区:BufferedReader和BufferedWreiter * 缓冲区的出现时为了提高流的操作效率而出现的. * 需要被提高效率的流作为参数传递给缓冲区的构造函数 * 在缓冲区中封装了一个数组,存入数据后一次取出
BufferedReader示例:
读取流缓冲区提供了一个一次读一行的方法readline,方便对文本数据的获取。
readline()只返回回车符前面的字符,不返回回车符。如果是复制的话,必须加入newLine(),写入回车符
newLine()是java提供的多平台换行符写入方法。
[java] view plain copyimport java.io.*; class BufferedReaderDemo { public static void main(String[] args) throws IOException { //创建一个字符读取流流对象,和文件关联 FileReader rw = new FileReader("buf.txt"); //只要将需要被提高效率的流作为参数传递给缓冲区的构造函数即可 BufferedReader brw = new BufferedReader(rw); for(;;) { String s = brw.readLine(); if(s==null) break; System.out.println(s); } brw.close();//关闭输入流对象 } }
BufferedWriter示例:
[java] view plain copyimport java.io.*; class BufferedWriterDemo { public static void main(String[] args) throws IOException { //创建一个字符写入流对象 FileWriter fw = new FileWriter("buf.txt"); //为了提高字符写入效率,加入了缓冲技术。 //只要将需要被提高效率的流作为参数传递给缓冲区的构造函数即可 BufferedWriter bfw = new BufferedWriter(fw); //bfw.write("abc\r\nde"); //bfw.newLine();这行代码等价于bfw.write("\r\n"),相当于一个跨平台的换行符 //用到缓冲区就必须要刷新 for(int x = 1; x < 5; x++) { bfw.write("abc"); bfw.newLine(); //java提供了一个跨平台的换行符newLine(); bfw.flush(); } bfw.flush(); //刷新缓冲区 bfw.close(); //关闭缓冲区,但是必须要先刷新 //注意,关闭缓冲区就是在关闭缓冲中的流对象 fw.close(); //关闭输入流对象 } }
2.装饰设计模式
装饰设计模式::::
要求:自定义一些Reader类,读取不同的数据(装饰和继承的区别)
MyReader //专门用于读取数据的类
|--MyTextReader
|--MyBufferTextReader
|--MyMediaReader
|--MyBufferMediaReader
|--MyDataReader
|--MyBufferDataReader
如果将他们抽取出来,设计一个MyBufferReader,可以根据传入的类型进行增强
class MyBufferReader {
MyBufferReader (MyTextReader text) {} MyBufferReader (MyMediaReader media) {} MyBufferReader (MyDataReader data) {} }
但是上面的类拓展性很差。找到其参数的共同类型,通过多态的形式,可以提高拓展性
class MyBufferReader extends MyReader{ private MyReader r; //从继承变为了组成模式 装饰设计模式 MyBufferReader(MyReader r) {} }
优化后的体系:
|--MyTextReader
|--MyMediaReader
|--MyDataReader
|--MyBufferReader//增强上面三个。装饰模式比继承灵活,避免继承体系的臃肿。降低类与类之间的耦合性
装饰类只能增强已有的对象,具备的功能是相同的。所以装饰类和被装饰类属于同一个体系
MyBuffereReader类: 自己写一个MyBuffereReader类,功能与BuffereReader相同
[java] view plain copyclass MyBufferedReader1 extends Reader{ private Reader r; MyBufferedReader1(Reader r){ this.r = r; } //一次读一行数据的方法 public String myReaderline() throws IOException { //定义一个临时容器,原BufferReader封装的是字符数组。 //为了演示方便。定义一个StringBuilder容器。最终要将数据变成字符串 StringBuilder sb = new StringBuilder(); int ch = 0; while((ch = r.read()) != -1) { if(ch == '\r') continue; if(ch == '\n') //遇到换行符\n,返回字符串 return sb.toString(); else sb.append((char)ch); } if(sb.length()!=0) //当最后一行不是以\n结束时候,这里需要判断 return sb.toString(); return null; } /* 需要覆盖Reader中的抽象方法close(),read(); */ public void close()throws IOException { r.close(); } public int read(char[] cbuf,int off, int len)throws IOException { //覆盖read方法 return r.read(cbuf,off,len); } public void myClose() throws IOException{ r.close(); } }一、字节流
1.概述:
1、字节流和字符流的基本操作是相同的,但是要想操作媒体流就需要用到字节流。
2、字节流因为操作的是字节,所以可以用来操作媒体文件。(媒体文件也是以字节存储的)
3、读写字节流:InputStream 输入流(读)和OutputStream 输出流(写)
4、字节流操作可以不用刷新流操作。
5、InputStream特有方法:
int available();//返回文件中的字节个数
注:可以利用此方法来指定读取方式中传入数组的长度,从而省去循环判断。但是如果文件较大,而虚拟机启动分配的默认内存一般为64M。当文件过大时,此数组长度所占内存空间就会溢出。所以,此方法慎用,当文件不大时,可以使用。
练习:
需求:复制一张图片F:\java_Demo\day9_28\1.BMP到F:\java_Demo\day9_28\2.bmp
[java] view plain copyimport java.io.*; class CopyPic { public static void main(String[] args){ copyBmp(); System.out.println("复制完成"); } public static void copyBmp() { FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream("F:\\java_Demo\\day9_28\\1.bmp"); //写入流关联文件 fos = new FileOutputStream("F:\\java_Demo\\day9_28\\2.bmp"); //读取流关联文件 byte[] copy = new byte[1024]; int len = 0; while((len=fis.read(copy))!=-1) { fos.write(copy,0,len); } } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("复制文件异常"); } finally { try { if(fis!=null) fis.close(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("读取流"); } } } }2. 字节流缓冲区
字节流缓冲区跟字符流缓冲区一样,也是为了提高效率。
注意事项:
1. read():会将字节byte()提升为int型值2. write():会将int类型转换为byte()类型,保留最后的8位。
练习:
1.复制MP3文件 1.MP3 --> 2.MP3
2.自己写一个MyBufferedInputStream缓冲类,提升复制速度
代码:
[java] view plain copyimport java.io.*; //自己的BufferedInputStream class MyBufferedInputStream { private InputStream in; //定义一个流对象 private byte [] buf = new byte[1024*4]; private int count = 0,pos = 0; public MyBufferedInputStream(InputStream in){ this.in = in; } public int MyRead() throws IOException{ if(count==0) { //当数组里的数据为空时候,读入数据 count = in.read(buf); pos = 0; byte b = buf[pos]; count--; pos++; return b&255; //提升为int类型,在前面三个字节补充0。避免1111 1111 1111 1111 } else if(count > 0) { byte b = buf[pos]; pos++; count--; return b&0xff; //提升为int类型,在前面三个字节补充0。避免1111 1111 1111 1111 } return -1; } public void myClose() throws IOException{ in.close(); } } class BufferedCopyDemo { public static void main(String[] args) { long start = System.currentTimeMillis(); copy(); long end = System.currentTimeMillis(); System.out.println("时间:"+(end-start)+"ms"); start = System.currentTimeMillis(); copy1(); end = System.currentTimeMillis(); System.out.println("时间:"+(end-start)+"ms"); } public static void copy1() { //应用自己的缓冲区缓冲数据 MyBufferedInputStream bis = null; BufferedOutputStream bos = null; try { bis = new MyBufferedInputStream(new FileInputStream("马旭东-入戏太深.mp3"));//匿名类,传入一个InputStream流对象 bos = new BufferedOutputStream(new FileOutputStream("3.mp3")); int buf = 0; while((buf=bis.MyRead())!=-1) { bos.write(buf); } } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("复制失败"); } finally { try { if(bis!=null) { bis.myClose(); bos.close(); } } catch (IOException e) { e.printStackTrace(); } } } }二、流操作规律
1. 键盘读取,控制台打印 ##。
System.out: 对应的标准输出设备:控制台 //它是PrintStream对象,(PrintStream:打印流。OutputStream的子类)System.in: 对应的标准输入设备:键盘 //它是InputStream对象
示例:
[java] view plain copy/*================从键盘录入流,打印到控制台上================*/ public static void InOutDemo(){ //键盘的最常见的写法 BufferedReader bufr = null; BufferedWriter bufw = null; try { /*InputStream ips = System.in;//从键盘读入输入字节流 InputStreamReader fr = new InputStreamReader(ips);//将字节流转成字符流 bufr = new BufferedReader(fr); *///将字符流加强,提升效率 bufr = new BufferedReader(new InputStreamReader(System.in)); //匿名类InputSteamReader:读取字节并将其解码为字符 bufw = new BufferedWriter(new OutputStreamWriter(System.out)); //OutputStreamWriter:要写入流中的字符编码成字节 String line = null; while((line = bufr.readLine())!=null){ if("over".equals(line)) break; bufw.write(line.toUpperCase()); //打印 bufw.newLine(); //为了兼容,使用newLine()写入换行符 bufw.flush(); //必须要刷新。不然不会显示 } if(bufw!=null) { bufr.close(); bufw.close(); } } catch (IOException e) { e.printStackTrace(); } }
}
2. 整行录入
1.从键盘录入数据,并存储到文件中。
我们在键盘录入的是时候,read()方法是一个一个录入的,能不能整行的录入呢?这时候我们想到了BufferedReader中ReadLine()方法。
3. 转换流
为了让字节流可以使用字符流中的方法,我们需要转换流。
InputStreamReader:字节流转向字符流;
a、获取键盘录入对象。
InputStream in=System.in;
b、将字节流对象转成字符流对象,使用转换流。
InputStreamReaderisr=new InputStreamReader(in);
c、为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
BufferedReaderbr=new BufferedReader(isr);
//键盘录入最常见写法
BufferedReaderin=new BufferedReader(new InputStreamReader(System.in));
2.OutputStreamWriter:字符流通向字节流
示例:
[java] view plain copy/*================把键盘录入的数据存到一个文件中==============*/ public static void inToFile() { //键盘的最常见的写法 BufferedReader bufr = null; BufferedWriter bufw = null; try { /*InputStream ips = System.in; //从键盘读入输入字节流 InputStreamReader fr = new InputStreamReader(ips); //将字节流转成字符流 bufr = new BufferedReader(fr); *///将字符流加强,提升效率 bufr = new BufferedReader(new InputStreamReader(System.in)); //匿名类。InputSteamReader:读取字节并将其解码为字符 bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("out.txt"))); //OutputStreamWriter:要写入流中的字符编码成字节 String line = null; while((line = bufr.readLine())!=null){ if("over".equals(line)) break; bufw.write(line.toUpperCase()); //打印 bufw.newLine(); //为了兼容,使用newLine()写入换行符 bufw.flush(); //必须要刷新。不然不会显示 } if(bufw!=null) { bufr.close(); bufw.close(); } } catch (IOException e) { e.printStackTrace(); } }
4. 流操作基本规律
为了控制格式我将其写入了Java代码段中,如下:
示例1:文本 ~ 文本
[java] view plain copy /* 流操作的基本规律。 一、两个明确:(明确体系) #1. 明确源和目的 源:输入流 InputStream Reader 目的:输出流 OutputStream Writer 2. 操作的数据是否是纯文本 是: 字符流 否: 字节流 二、明确体系后要明确具体使用的对象 # 通过设备区分:内存,硬盘,键盘 目的设备:内存,硬盘,控制台
示例1:将一个文本文件中的数据存储到另一个文件中: 复制文件
一、明确体系
源:文件-->读取流-->(InputStream和Reader)
是否是文本:是-->Reader
目的:文件-->写入流-->(OutputStream Writer) 是否纯文本:是-->Writer 二、 明确设备 源:Reader 设备:硬盘上一个文本文件 --> 子类对象为:FileReader FileReader fr = new FileReader("Goods.txt"); 是否提高效率:是-->加入Reader中的缓冲区:BufferedReader BufferedReader bufr = new BufferedReader(fr); 目的:Writer 设备:键盘上一个文本文件 --> 子类对象:FileWriter FileWriter fw = new FileWriter("goods1.txt"); 是否提高效率:是-->加入Writer的缓冲区:BufferedWriter BufferedWriter bufw = new BufferedWriter(fw);
示例2:将一个图片文件数据复制到另一个文件中:复制文件
一、明确体系
源:文件-->读取流-->(InputStream和Reader)
是否是文本:否-->InputStream
目的:文件-->写入流-->(OutputStream Writer) 是否纯文本:否-->OutputStream 二、 明确设备 源:InputStream 设备:硬盘上一个媒体文件 --> 子类对象为:FileInputStream FileInputStream fis = new FileInputStream("Goods.txt"); 是否提高效率:是-->加入InputStream中的缓冲区:BufferedInputStream BufferedInputStream bufi = new BufferedInputStream(fis); 目的:OutputStream 设备:键盘上一个媒体文件 --> 子类对象:FileOutputStream FileOutputStream fos = new FileOutputStream("goods1.txt"); 是否提高效率:是-->加入OutputStream的缓冲区:BufferedOutputStream BufferedOutputStream bufo = new BufferedOutputStream(fw);
示例3:将键盘录入的数据保存到一个文本文件中
一、明确体系
源:键盘-->读取流-->(InputStream和Reader)
是否是文本:是-->Reader
目的:文件-->写入流-->(OutputStream Writer) 是否纯文本:是-->Writer 二、 明确设备 源:InputStream 设备:键盘 --> 对用对象为:System.in --> InputStream 为了操作方便,转成字符流Reader --> 使用Reader中的转换流:InputStreamReader InputStreamReader isr = new InputStreamReader(System.in); 是否提高效率:是-->加入Reader中的缓冲区:BufferedReader BufferedReader bufr = new BufferedReader(isr); 目的:Writer 设备:键盘上一个文本文件 --> 子类对象:FileWriter FileWriter fw = new FileWriter("goods1.txt"); 是否提高效率:是-->加入Writer的缓冲区:BufferedWriter BufferedWriter bufw = new BufferedWriter(fw);
5.指定编码表(转换流可以指定编码表 ##)
要求:用UTF-8编码存储一个文本文件
[java] view plain copyimport java.io.*; public class IOStreamLaw { /** * @param args */ public static void main(String[] args) throws IOException { //键盘的最常见写法 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("goods1.txt"),"UTF-8")); String line = null; while((line=bufr.readLine())!=null){ if("over".equals(line)) break; bufw.write(line.toUpperCase()); bufw.newLine(); bufw.flush(); } bufr.close(); } }