JAVASE复习
六花酱赛高
1. 外部类可以通过非静态内部类对象访问内部类的private属性,内部类可以直接访问外部类的private属性,说明外部类和内部类都是在“一个类中”,这样才能相互访问到对方的private属性
2. 非静态内部类的实例化:new Outer().new Inner();//这个很好理解,Inner需要用outer对象来实例化,Inner的命名空间是Outer
3. 静态内部类的实例化: new Outer.Inner();
4. 内部类的子类的构造器调用父类构造器,要加上外部类做为命名空间:
SubInner.java code:
1 class Outer1 { 2 class Inner {//注意权限限定,别private了,不然没法继承。如果是protected和默认权限,也要注意SubInner放在哪 3 private String name; 4 public Inner(String name) { 5 this.name = name; 6 } 7 } 8 } 9 10 public class SubInner extends Outer1.Inner { 11 public SubInner(Outer1 out, String name) { 12 out.super(name); 13 //super(name);//是错的,说明内部类是有命名空间的 14 //Outer1.super(name);//是错的,说明非静态内部类必须要存在于一个父类的实例中 15 } 16 }
5. 内部类生成的class文件:Outer$Inner.class
6. 局部内部类(在方法中定义的内部类)生成的class文件:OuterClass$NInner.class,局部内部类可能重名,所以N从1开始递增
7. 个人觉得java使用内部类实现闭包和回调华而不实
1. switch语句可以用枚举类来做判断
2. Enum可以implements interface但是不能 extends class。原因是enum自动继承java.lang.Enum,不能再继承其他的类了!
3. 枚举类的对象在定义时可用类似与匿名内部类的方式来重写方法、添加方法、添加属性等
4. 枚举类默认是public final的。但可以使用匿名内部类的方式来给枚举类对象初始化,是因为写匿名内部类的时候jvm自动把枚举类弄成abstract而不使用final,但这个abstract不能让你显式的写上去,而且在其他地方不能让你继承枚举类,就算是匿名内部类也不行,如:
Season.java code:
1 //class A extends Season {//错的,除了给枚举对象初始化时可以继承,其他地方都不能继承 2 // 3 //} 4 public enum Season {//extends 是被不允许的,因为java没有多继承 5 SPRING() {//使用匿名内部类给枚举对象初始化 6 int a; 7 8 @Override 9 void func() { 10 11 } 12 }; 13 abstract void func();//在enum中可以写抽象方法 14 public static void main(String[] args) { 15 //Season s = new Season() {};错,即使是匿名内部类继承,也不能使用 16 } 17 }
- 对象的三个状态:可达、可恢复、不可达
- System.gc() = Runtime.getRuntime().gc()
System.runFinalization() = Runtime.getRuntime().runFinalization()
3. System.gc()之后Thread.sleep(int time), 几秒之后效果更好
4. System.gc()之后System.runFinalization()类似于第三点,如:
Main.java code:
1 public class Main { 2 public static void main(String[] args) throws Exception { 3 for (int i = 0; i < 1000; ++i) { 4 new Main(); 5 } 6 System.gc(); 7 //Thread.sleep(10); 8 System.runFinalization(); 9 System.out.println("如果\"清理\"出现在下面,说明没有及时清理"); 10 // System.out.println(Runtime.getRuntime().freeMemory()); 11 } 12 13 @Override 14 public void finalize() { 15 System.out.println("清理"); 16 } 17 }
5. Gc只是建议jvm收集垃圾! runFinalization只是建议jvm调用可恢复对象的finalize方法
6. 要想让gc更有效率,就得深入学习java垃圾回收机制以及优化,别再想强行释放的问题了
7. 程序结束时可能先关闭标准输出流,再调用垃圾对象的finalize方法,所以你不一定能在小程序中看到 finalize方法的输出。又或者调用finalize方法的途中关闭标准输出流,所以肯能只输出到一句,第 二句就不输出了
8. 讲到gc,就得知道java从1.2开始的4中引用:强引用、软引用、弱引用、虚引用
a) 平常用的就是强引用,强引用全部赋值为null时会回收,不一定会马上回收
b) 软引用SoftReference,在虚拟机内存不足的时候会释放,使用前先把强引用关
联软引用,然后把强引用赋值为null
c) 弱引用WeakReference,不管怎样,都回收,不一定立即回收。使用方法同上
d) 虚引用PhantomReference,无法从虚引用获得对象,与引用队列ReferenceQueue
联用,用来查看对象是否以被回收,如果被回收,那么引用队列中存在那个虚引用
strictfp: 用来修饰浮点数,将严格按照IEEE-754来计算,使得计算更精确
native: 本地方法,一般用C语言来写,用来调用一些跟平台有关的函数或链接库,定义之后
说明你的程序不再具有平台无关性。
使用方法:
1. 写native的方法声明,别写方法体
2. 用javah编译第一部生成的class文件,将产生一个.h文件(C语言中叫头文 件)。
3. 写一个.cpp文件实现native方法,其中需要包含第一部产生的.h头文件(.h 文件中又包含了JDK自带的jni.h文件).
4. 将第二部中的.cpp文件变异成动态链接库文件。
5. 在Java中调用System.loadLibrary()方法或Runtime的loafLibrary()方法加载 第三部产生的动态链接库文件,
就可以在Java中调用这个native()方法了。
transient: Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,
我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,
可以在这个域前加上关键字transient。当一个对象被序列化的时候,
transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
volatile: 它是被设计用来修饰被不同线程访问和修改的变量,当变量被修改时,jvm会通知其他线程该变量已修改
- Scanner实例化时可指定输入流,也可以指定文件,不只有标准输入System.in哦~
用Scanner一行一行地读取文件内容也是很方便的哦~
2. System代表当前java程序的运行平台,不能实例化。提供标准输入、输出、错误,
可以得到环境变量等系统参数
3. Runtime代表当前java的运行时环境,不能实例化,只能通过Runtime.getRuntime()
获取实例,可以得到处理器数量、空闲内存数等参数。可以另外启动一个进程来运行操作
系统的命令
4. 工具类一般以s结尾,如Arrays,Collections,java7新增的Objects
5. BigDicemal实例化时要用字符串作为构造器参数,否则浮点数后面几位的随机数会导致精度
不好
1. 集合:两个接口: Collection和Map, collection下有三个接口:List、Queue、Set
2. 集合只能保存对象,list.add(5);正确是由于自动装箱
3. Iterator本身并不提供盛装对象的能力,有iterator对象必有一个Collection对象
4. 在使用iterator遍历集合的时候,想要删除元素只能通过it.remove()删除当前
元素,不能通过collection.remove(Object);来删除元素,否则极有可能可能会报
java.util.ConcurrentModificationException异常
5. foreach同上
6. HashSet插入时调用hashCode和equals来比较两个对象,hashCode一样且
equals为true,那就不插入。如果equals为false,hashCode不一样,
那就插在不同位置上(hashCode决定位置)。如果equals为false且hashCode
相等,那就插在同一个位置上的链表上(hash冲突)
7. 不同类型的Field取得hashCode值的方式:
boolean: hashCode=(f ? 0 : 1);
整数类型:hashCode=(int)f;
long: hashCode=(int)(f ^ (f >>> 32));
float: hashCode=Float.floatToIntBits();
double: long l = Double.doubleToLongBits(); hashCode=(int)(f ^ (f >>> 32));
引用类型: hashCode=字段.hashCode();
8. LinkedHashSet输出元素的顺序与插入元素的顺序一致。可能仅仅通过hashCode计算相对位置,
比如说只插入a和b元素,a的hashCode是5,b的hashCode是8,那么a的next直接指向b,如果
要插入c(hashCode=7),那么会插入在a和b之间。总之不会像数组哈希表一样空出下标为6和7的
位置
9. TreeSet使用红黑树作为数据结构,因此是有序的,效率比HashSet差一些。插入元素时,除第一个
被插入的元素外,其他都要实现Comparable接口(当然最好也要给第一个实现);如果指定了比较
器Comparator,可以不实现Comparable接口。
10. 固定长度的List:工具类Arrays的内部类ArrayList是固定的,它和java.util.ArrayList是不同的,
使用Arrays.asList(Object... a);返回的就是这个内部类,只能遍历,不能增加、删除元素
11. java实现了优先队列PriorityQueue
12. 双端队列接口: Deque,LinkedList实现了Deque,因此可以作为双端队列。ArrayDeque是数组
形式的双端队列
13. Hashtable不允许使用null作为key或value,HashMap可以使用一个null来作为key,n个null
作为value。装进它们的对象要重写hashCode和equals
14. LinkedHashMap 与 8. 类似
15. Properties可以读取XML配置(loadXml()),也可以写XML配置(storeToXml())
16. WeakHashMap:装弱引用的hashmap。一般用来装一些不是要一直存在内存中的对象
17. IdentityHashMap: 用==来比较
18. HashSet和HashMap可以设置初始化容量以及负载极限。高负载极限换空间,低负载极限换时间
19. 工具类Collections可以对集合排序、反转等操作,还能将非线程安全的集合类变成线程安全的
集合类:synchronizedXxx();
如: Collections.synchronizedCollection(new ArrayList);
Collections.synchronizedSet(new HashSet());
Collections.synchronizedMap(new HashMap());
-Xms128m: 设置jvm堆内存初始大小为128M
-Xmx512m: 设置jvm最大对内存大小为512M
-verbose:gc 打印gc情况
1. java7的菱形语法: List<String> list = new ArrayList<>();
2. 泛型的继承:
class A<T> { public T get(T t) { //T t = new T();T不能实例化 return t; } } class B extends A {//继承A<String>或A都可以,但不能是A<T>,要想继承A<T>得写 class B<T> extends A<T> //get方法:如果继承A就不能加Override注释(raw type),继承了A<String>可以加注释 public String get(String s) { return s; } }
2. 泛型参数T只存在于编译期,在运行期期的时候将T给擦除并替换成xxx
3. A<String>和A<Integer>是同一个类,因此getClass相等, 它们俩被当成同一个A类。
4. class A<T>, 由于同一个类共用一段栈内存来存放类成员,因此静态属性、静态方法、静态代码段都不能有泛型参数T,可以有其他泛型参数如<? extends Object>
5. 另外A<xxx>类是不存在的,因此不能用instanceof A<xxx>可以用instanceof A
6. A<Integer>并不是A<Number>的子类,要想同时使用Number和Integer可以把A写成:class A< ? extends Number>
7. class B extends A,那么可以写A[] a = new B[10];(A[]是B[]的父类,反过来赋值不行), 但是泛型不能这么写:A<Integer>[] a = new B<Interger>[10];
8. List<?> list = new ArrayList<String>();是对的,但是list.add(new Object());会报错,因为List<E>类的add方法是:boolean add(E e)。声明为List<?>将会导致add的方法的参数不知道是什么类型的,所以报错。然后add(null);是对的,因为null可以是所有引用类型的实例
9. 泛型参数可设定多个上限,如 class A<T extends Number & java.io.Serializable>,如果有类上限,那么类上限必须在第一位。用&隔开多个上限
10. java7的菱形语法与泛型构造器:
1 public class A { 2 public <T> A(T t) { 3 4 } 5 public static void main(String[] args) { 6 new A("123");//与下面一句效果一样 7 new <String> A("123"); 8 new A(1);//与下面一句效果一样 9 A a = new <Integer> A(123); 10 } 11 }
11. javac增加命令-Xlint:unchecked可以忽略unchecked异常,系统会把class A<T>的T当成Object来处理
12. 可以指定多个泛型参数,如:class<T, S, R>
1. Throwable是异常父类,其下两个子类,Error、Exception。Error会导致程序中断,没法处理,所以别catch
2. 异常分为cheked异常和runtime异常,cheked异常是java
3. java7可以catch多个异常,如: catch(IndexOfBoundsException|NumberFormatException|ArithmeticException e)
4. 资源是没法自动回收的。资源一般有数据库连接、网络连接、磁盘文件等。因为try和catch不一定会完整执行, 所以资源需要在finally中手动关闭。在java7中可以自动关闭资源,这时需要使用java7的语法:
public class A { static void a() { try( BufferedReader br = new BufferedReader(new FileReader("A.java")); PrintStream ps = new PrintStream(new FileOutputStream("a.txt")); ) { /*try块*/ } catch(IOException e) { } } }
上面try后面有个括号,类似于方法参数列表。在try()中定义的变量可以在try、catch、finally中使用,无需提取到try之前。程序中隐含了一个自动关闭br和ps的finally块。如果你愿意,可以自己写finally
5. catch块和finally块必须出现其中一个或者两者都出现。java7之后,可以一个都不写:参见第4点
6. Runtime异常无需显示声明抛出,如果需要捕获,也可以catch。常见的runtime异常为NullPointerException、IndexOuterOfBoundsException、ArithmeticException,这些异常继承了RuntimeException
出错会导致程序中断,感觉RuntimeException应该继承Error,但实际上继承的是Exception,好奇怪
7. 大程序中经常在catch中throw异常,throw的是包装过之前的异常的异常或者是新异常。这样利于安全性,避免暴露底层细节,而且能以另一种更易理解的方式传达给catch异常的人。比如dao层catch了“数据库连接”异常,然后经过包装成“数据库帐号密码错误”异常或者重新new一个“数据库帐号密码错误”异常再throw给service层处理。
8. java7的throw比较强大。你的方法内部throw了IOException,你可以在catch中throw Exception,而方法throws的是IOException。当然你得确定你抛出的Exception实际上真的是IOException,如:
class A { static void a() throws IOException { throw new IOException(); } } class B { static void b() throws IOException {//注意是IOException,java7会仔细检查e是什么类型 try { A.a(); } catch (Exception e) { throw e;//抛出的是Exception } } }
9. 异常链,应用了设计模式中的职责链模式。就是一个异常包装另一个异常,把一个Exception传给异常的构造器,如
new IOException(new Exception);作用在第7点
10. 在正式发布的程序中,应该避免使用printStackTrace()处理异常,而应该要进行业务逻辑的处理,并把异常信息写进日志文件
11. 不要过度使用异常,要将业务逻辑和异常处理分离开,比如验证用户输入的合法性最好用业务逻辑来处理,不要用异常处理。一是异常处理效率慢,二是业务逻辑只用if else来判断即可,较为简单,三是尽量减少业务和异常的耦合
1. 使用之前先把mysql服务打开(mysqld-nt.exe),以及把mysql的环境变量给加上(指定到bin目录)
2. 连接mysql: mysql -h hostname -u username -ppassword 注意-p后面没空格
或 mysql -u username -p
3. 显示所有的数据库: show databases;
4. 创建数据库: create database [IF NOT EXISTS] dbName; IF NOT EXISTS是可选的
5. 删除数据库: drop database dbName;
5. 切换当前数据库: use dbName;
6. 显示当前数据库的所有表: show tables;
7. 显示表里的字段信息: desc tableName;
8. 退出mysql: quit/exit
9. windows手动mysql服务: http://www.lc365.net/blog/b/14461/
10. mysql数据库支持两种存储机制。一种是MyISAM,这是mysql早期的默认存储机制,对事务支持不够好。第二种是InnoDB,通过建立行级锁来保证事务完整性,并以Oracle风格的共享锁来处理Select语句,系统默认启动InnoDB存储机制,如果不想使用InnoDB可以使用skip-innodb选项。在建表时可以设定存储机制:如
ENGINE=MyISAM ENEGINE=InnoDB,如:
create table test1(id int) ENGINE=MyISAM;
11. sql分为5种类型:
a) 查询语句,如select语句
b) DML(Data Manipulation Language, 数据库操作语言), 主要有insert、update和delete
c) DDL(Data Definition Language, 数据定义语言), 主要有create、alter、drop、truncate(先删除表再重新创建表,速度很快)
d) DCL(Data Control Language, 数据控制语言), 主要有grant和revoke ----------都是设置权限的关键字
e) 事务控制语句,主要有commit,rollback、 savepoint
12. sql不分大小写
13. 常见的数据库对象,下表摘自疯狂java讲义:
对象名称 | 对应关键字 | 描述 |
表 | table | 表就是存储数据的逻辑单元,以行和列的形式存在;列就是字段,行就是记录 |
数据字典 |
就是系统表,存放数据库相关信息的表。系统表里的数据通常由数据库系统维护, 程序员不应该手动修改系统表以及内部数据, 程序员只可查看系统表的数据 |
|
约束 | constraint | 执行数据校验的规则,用于保证数据完整性的规则 |
视图 | view | 一个或多个数据表里数据的逻辑显示。视图并不存储数据 |
索引 | index | 用于提高查询性能,相当于书的目录 |
函数 | function | 用于完成一次特定的计算,具有一个返回值 |
存储过程 | procedure |
用于完成一次完整的业务处理,没有返回值,但通过传出参数将多个值 传给调用环境 |
触发器 | trigger |
相当于一个事件监听器,当数据库发生特定事件后,触发器被触发,完 成响应的处理 |
14. DDL可以操作数据库对象比如create table, create index, create view。要记住,操作数据库对象的是DDL
15. 创建表时可以设置字段默认值如: id varchar(255) default 'xxx'
16. 可以通过子查询创建一场表,如:create table t as select * from user_inf; 这样创建的t表就具有而后user_inf表一样的字段和数据
17. DDL常用操作:
增加字段: alter table tableName add(aaa varchar(255) default 'xxx', bbb varchar(255)); 或alter table tableName add aaa varchar(255);
增加字段时要注意除非为新增的列指定默认值,否则新增额数据列不可指定非空约束
修改字段: alter table tableName modify xxxxx varchar(12) not null, modify article varchar(12) not null default 'abc'; mysql修改多个字段只能增加多个modify语句如:
删除字段: alter table tableName drop xxxxx, drop content;
给表重命名: alter table tableName rename to tt;
MySQL的change关键字: alter table tableName change xxx aaa varchar(20) default '998xxoo' not null;//把xxx变成aaa,并改变类型、默认值和非空约束
18. 数据库约束
a) NOT NULL
b) UNIQUE
c) PRIMARY KEY
d) FOREIGN KEY
e) CHECK 指定一个布尔表达式,用于指定对应列的值必须满足该表达式
f) 单列约束
g) 多列约束
19. MySQL的语句中可以使用check约束,但是不会有任何作用
20. MySQL使用information_schema数据库里的TABLE_CONSTRAINTS表来保存该数据库实例中所有的约束信息
21. 取消非空约束:把语句中的not null 改成null 就好了
22. UNIQUE约束: 保证指定列或指定列组不允许出现重复值,但是可以出现多个null值,因为在数据库中null不等于null。同一个表内可建多个唯一约束,,唯一约束也可由多列组成。当为某列创建唯一约束时,MySQL会为该列响应地创建唯一索引。如果不给唯一约束秦明,该唯一约束默认与列名相同。
23. PRIMARY KEY相等等于UNIQUE加上NOT NULL。主键自增长:可以在整形字段的后边加上 auto_increment
24. FOREIGN KEY,要注意references的是主表的主键,主键和外键的个数和类型都是一致的。一般用外键来表示一对多、多对多的关系
25. 创建UNIQUE、PRIMARY KEY、FOREIGN KEY的语句很类似,删除这三个约束的语句也很类似。
建立上面三个约束有两种写法:
1) 列级约束语法:这样做只能修改一列,而且不能指定约束名称
create table teacher( teacher_id int primary key, teacher_name varchar(10) unique ); create table student( student_id int primary key, student_name varchar(10) unique, teacher_id int references teacher(teacher_id)#虽然能够运行,使用列级约束语法建立外键在MySQL中无效
);
2) 表级约束语法:可以让多列组成一个约束,并且可以给约束命名
create table if not exists teacher( teacher_id int, teacher_id2 int, teacher_name varchar(10), teacher_name2 varchar(10), teacher_name3 varchar(10), teacher_name4 varchar(10), teacher_name5 varchar(10), constraint teacher_pk primary key(teacher_id, teacher_id2),#设置primary key unique(teacher_name, teacher_name2),#设置unique key,或者下面的 constraint teacher_uk unique(teacher_name3, teacher_name4) ) ENGINE=INNODB; create table student( student_id int primary key, student_name varchar(10) unique, teacher_id int, teacher_id2 int, #foreign key(teacher_id, teacher_id2) references teacher(teacher_id, teacher_id2),#设置外键,或者下面的 constraint student_teacher_fk foreign key(teacher_id, teacher_id2) references teacher(teacher_id, teacher_id2) ) ENGINE=INNODB;
26. 修改约束:
drop index unique_name;#MySQL删除unique约束的写法,一般数据库用drop constraint
alter table student
drop primary key;#删除主键约束,不需要名字
alter table student
drop foreign key studnet_teacher_fk;#删除外键约束,需要名字
#增加上面的三个约束,均可以用alter tableName add constraint语句
#修改上面的三个约束,均可以用alter tableName modify 语句
27. CHECK约束
如:当然这在MySQL不起效果,仅仅是为了符合SQL标准
create table student( student_id int primary key, student_name varchar(10) unique, student_score float check(student_score <= 100 && student_score >=0) ) ENGINE=INNODB;
28. 索引是存放在模式(scheme)中的一个数据库对象,虽然索引总是从属于数据表,但它也和数据表一样术语数据库对象。创建索引的唯一作用就是加速对表的查询,索引通过使用快速路径访问方法来快速定位数据,从而减少磁盘的I/O。索引作为数据库对象,在数据字典中独立存放,但不能独立存在,必须属于某个表。MySQL使用information_schema数据库中的STATISTICS表来保存该数据库实例中的所有索引信息
29. 创建索引有两种方式
自动:当在表上定义主键约束、唯一键约束和外键约束时,系统会为该字段创建对应的索引(约束不是索引,只不过创建约束时系统自动加上索引)
手动:create index indexName on tableName(columnName, ...);
30. 删除索引的两种方式
自动:删除表时,索引自动被删除
手动: drop index indexName on tableName;
31. Oracle删除索引不需要指定表名,因为索引不需要重名。而MySQL各表中的索引可以重名,表内不可以重名,所以需要指定索引
32. 索引可以加速查询,但是需要系统开销
33. 视图看起来像数据表,但不是。它不能存储数据,而只是关联了一个查询结果(即关联了sql语句)。
使用视图的好处:可以限制对数据的访问
可以使复杂的查询变得简单
提供数据的独立性(没看懂)
提供对相同数据的不同显示(没看懂)
使用方法:
create or replace view view_test as select student_id from student; with check option;
一般视图是不让修改视图里的数据的,所以可以在最后加上 with check option
34. 自关联,一个表的外键引用了这个表自己的主键。如员工表中员工的上司id字段指向上司的id
create table forign_test ( foreign_id int auto_increment primary key, foreign_name varchar(55), refer_id int, foreign key(refer_id) references forign_test(foreign_id) );
35. 如果想删除主表记录时也删除子表记录可以在写外键时加上 on delete cascade,如:
foreign key(refer_id) references forign_test(foreign_id) on delete cascade
如果想删除主表记录时,让子表记录赋值为null,可以加上 on delete set null
(外键本身没有非空约束,所以外键可以为null,但是不能为其他主表的主键没有的值。如果想让外键必须是主表的主键的值,那就加上非空约束)
36. 多表连接查询
多表连接查询有两种规范,SQL92和SQL99
SQL92提供的多表连接:
等值连接
非等值连接(大于、小于等等)
外连接
广义笛卡尔积
SQL99提供的多表连接:(提供了可读性更好的多表连接语法)
交叉连接
自然连接
使用using子句的连接
使用on子句的连接
全外连接或左、右外连接
37. 进行多表连接查询时千万要注意不要写成笛卡尔积,效率掉渣了。除非你非得用笛卡尔积查询
38. MySQL不支持SQL92的左外、右外连接,以及SQL99的全外连接
39. 自连接,查询第34点所说的自关联表时用到的查询。把一张表当成两张表就行(两个别名)。自连接只是连接的一种用法,而不是一种连接类型。
40. 交叉连接--笛卡尔积 cross join
如: select s.*, teacher_name from student_table s cross join teacher_table t;
41. 自然连接: 自然连接会以两个表中的同名列作为连接条件;如果没有同名列,就变成笛卡尔积
join ... natural
如:select s.*, teacher_name from student_table s natural join teacher_table t;
42. join ... using子句连接: 可以显式指定一列或多列同名列,如果不是同名列会报错
如: select s.* , teacher_name from student_table s join teacher_table t using(teacher_id);
43. join ... on子句连接: join... on... on 相当等于from ... where
如 select s.* , teacher_name from student_table s join teacher_table t on s.java_teacher > t.teacher_id;
可以用于等值连接、非等值连接
44. 左、右。 使用on来连接条件(不可缺少)
select s.* , teacher_name from student_table s left join teacher_table t on s.java_teacher > t.teacher_id; select s.* , teacher_name from student_table s right outer join teacher_table t on s.java_teacher < t.teacher_id;#outer可省略 select s.* , teacher_name from student_table s full join teacher_table t on s.java_teacher = t.teacher_id;
45. 子查询
子查询出现在两个地方:
1) from后边,这时被当作数据表,这种用法也被称为行内视图,因为该子查询的实质就是一个临时视图
2) where后边
当子查询返回一个值时,可以用 =、>、<比较
当子查询返回多个值时,可以在子查询的括号前面用in any all,要注意三者与>、<联用时的区别
46. 集合运算
使用集合运算,必须保证两个查询结果返回的字段数量一直而且类型一一相同。最后的结果以第一个表的字段名做为字段名
union:并集
如select * from teacher union select student_id, student_name from student;
minus: 差集/余集
用法如union,但MySQL不支持munus,可以用子查询实现:
select student_id, student_name from student where (student_id, student_name) not in (select teacher_id, teacher_name from teacher);
intersect:交集
用法如union,但MySQL不支持intersect,可以用子查询实现:
select student_id, student_name from student s join teacher t on (s.student_id = t.teacher_id and s.student_name = t.teacher_name);
如果求交集的两个select语句都用到了where,可以这样写:
select student_id, student_name from student s join teacher t on (s.student_id = t.teacher_id and s.student_name = t.teacher_name) where student_id < 4;
47. mysql事务
可以通过输入 set autocommit = 0; 来开启事务。
临时开启事务可以使用begin;或start transaction;来开启事务。
使用save point a;保存保存点
使用rollback;或rollback to a;返回事务开始之前或保存点a
48. MySQL使用数据库information_schema来保存系统表
tables
schemata
views
columns
triggers
rountines:存放存储过程和函数
key_column_usage:存放带约束的键信息
table_constraint:存放带约束的表信息
statistics:存放所有的索引信息
1. 使用executeUpdate执行DDL语句会返回0
2. java7可以使用try(){}语句来自动关闭ResultSet、PreparedStatement/Statement、Connection。java7之后,很多资源类被重写并实现了Closable接口或AutoClosable接口
3. 当不知道sql语句是查询还是更新时,可以执行Statement或PreparedStatement的exeucute方法
4. 如果设置SQL语句的参数时不知道类型,可以用PreparedStatement的setObject方法
5. PreparedStatement可以用于防止SQL注入,因为SQL写的是问号,替换参数时会把单引号给转义
6. 实例化Statement或PreparedStatement时,可以设置resultSetType的值:
ResulSet.TYPE_FORWARD_ONLY(只能往下移动),
ResultSet.TYPE_SCROLL_INSENSITIVE(可以自动移动,底层数据改变不影响ResultSet的内容),
ResultSet.TYPE_SCROLL_SENSITIVE(可以自由移动,底层数据改变影响ResultSet的内容)
还可以设置resultSetConcurrenty的值:(控制ResultSet的并发类型)
ResultSet.CONCUR_READ_ONLY,(默认,只读的并发模式)
ResultSet.CONCUR_UPDATABAL(可更新的并发模式);
可以这样实例化PreparedStatement:
PreparedStatement pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery();
rs.next();
rs.previous();//往前移一位
rs.absolute(100);//定位到第100条记录
rs.first();//定位到第一条记录
rs.last();//定位到最后一条记录 rs.updateString(1, "修改"); rs.updateRow();//这样就能在不另写一条update语句的情况下直接修改数据
7. Blob二进制长对象可以用来存储声音文件、图片文件
pstmt.setBlob(int parameterIndex, InputStream x); Blob blob = rs.getBlob(int columnIndex); blob.getBinaryStream(); blob.getBytes();
8. 如果不知道ResultSet中有哪些字段以及字段类型,可以通过ResultSetMeteData来获取关于ResultSet的表述信息
1 package c13_1; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.PreparedStatement; 6 import java.sql.ResultSet; 7 import java.sql.ResultSetMetaData; 8 9 10 11 public class Main { 12 public Main() { 13 try { 14 Class.forName("com.mysql.jdbc.Driver"); 15 } catch (ClassNotFoundException e1) { 16 e1.printStackTrace(); 17 } 18 } 19 public void test() { 20 try ( 21 Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydb", 22 "root", "root"); 23 PreparedStatement pstmt = conn.prepareStatement("select * from article"); 24 ResultSet rs = pstmt.executeQuery(); 25 26 ){ 27 ResultSetMetaData rsmd = rs.getMetaData();//ResultSetMetaData没有实现自动关闭 28 for (int i = 1; i <= rsmd.getColumnCount(); ++i) { 29 System.out.print(rsmd.getColumnName(i) + "(" + rsmd.getColumnTypeName(i) + ")\t");//从1开始 30 } 31 System.out.println(); 32 while (rs.next()) { 33 for (int i = 1; i <= rsmd.getColumnCount(); ++i) { 34 System.out.print(rs.getObject(i)); 35 } 36 System.out.println(); 37 } 38 }catch (Exception e) { 39 e.printStackTrace(); 40 } 41 System.out.println("ok"); 42 } 43 public static void main(String[] args) { 44 45 new Main().test(); 46 47 48 } 49 }
9.RowSet
如图,RowSet继承了ResultSet,其他5个接口在java6已经定义并有实现类:JdbcRowSetImpl、CachedRowsetImpl, WebRowSetImpl、FilteredRowSetImpl和JoingRowSetImpl
可以把RowSet当成ResulSet和Statement来使用,如JdbcRowSetImpl可以上下滚动、执行sql语句、执行sql、更新ResultSet。
但是JdbcRowSetImpl是内部专用API,可能会在未来的发行版中删除。使用JdbcRowSetImpl会让程序与它耦合,不利于后期的升级、拓展。
10. java7新增了RowSetProvider类和RowSetFactory接口,可以这样创建RowSet:
RowSetProvider.newFactory().createJdbcRowset();
RowSetProvider.newFactory().createFiltererRowset();
RowSetProvider.newFactory().createJoinRowset();
RowSetProvider.newFactory().createWebRowset();
通过这种方式创建的rowset并没有connection信息,所以需要传入connection对象或者传入url、username、password或者传入resultset
11. 离线rowset
对ResultSet的常见处理方式有两种
1:迭代ResultSet并封装到javabean,之后再不关闭connection。这样安全但麻烦
2:保持connection的打开状态,将resultset传到视图层使用,之后再关闭connection。这样对性能影响很大而且不安全
通过离线rowset可以解决这个问题:先关闭connection,再把离线rowset传到视图层使用,把离线rowset当成javabean使用。
CacheRowSet是所有离线rowset的父接口,使用第10点创建CacheRowSet,然后再把resultset传进来:
cacheRowSet.populate(resultSet);之后就能像ResultSet一样使用了,
另外还可以使用cacheRowSet.updateXxx("columnName", ?);和cacheRowSet.updateRow();更新一行数据
最后再得到新连接(通过连接池或DriverManager得到)并使用cacheRowSet.acceptChanges(connection);来把更新同步到数据库
12. 使用离线rowset进行分页查询
在dao层得到cacheRowSet后,通过调用cacheRowSet.setPageSize(int pageSize)和cacheRowSet.polulate(resultSet, (page-1)*pageSize+1);,
然后关闭connection并将rowset传到视图层,此时查出来的只有第 (page-1)*pageSize+1页的数据,这样可以防止CachedRowSet占用大量内存。
13. 批量更新
Statement接口有addBatch(String sql);和exeucteBatch();方法进行批量执行sql
14. DatabaseMetaData分析数据库信息
1 import java.sql.Connection; 2 import java.sql.DatabaseMetaData; 3 import java.sql.DriverManager; 4 import java.sql.ResultSet; 5 import java.sql.ResultSetMetaData; 6 7 8 9 public class Main { 10 public Main() { 11 try { 12 Class.forName("com.mysql.jdbc.Driver"); 13 } catch (ClassNotFoundException e1) { 14 e1.printStackTrace(); 15 } 16 } 17 public void test() { 18 try ( 19 Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydb", "root", "root"); 20 21 22 23 ){ 24 DatabaseMetaData dbmd = conn.getMetaData();//DatabaseMetaData没有实现自动关闭 25 System.out.println("获得所有支持的表的类型"); 26 ResultSet rs = dbmd.getTableTypes(); 27 printResulSet(rs); 28 29 System.out.println("获取当前数据库的所有表"); 30 rs = dbmd.getTables(null, null, "%", new String[]{"table"}); 31 printResulSet(rs); 32 33 System.out.println("获取某个表的主键"); 34 rs = dbmd.getPrimaryKeys(null, null, "article"); 35 printResulSet(rs); 36 37 System.out.println("获取当前数据库的所有存储过程"); 38 rs = dbmd.getProcedures(null, null, "%"); 39 printResulSet(rs); 40 41 System.out.println("获取某两个表中的外键:注意顺序是先主表后从表!!!!!!!!"); 42 rs = dbmd.getCrossReference(null, null, "teacher", null, null, "student"); 43 printResulSet(rs); 44 45 System.out.println("获取某个表所有字段"); 46 rs = dbmd.getColumns(null, null, "article", "%"); 47 printResulSet(rs); 48 49 }catch (Exception e) { 50 e.printStackTrace(); 51 } 52 System.out.println("ok"); 53 } 54 void printResulSet(ResultSet rs) throws Exception { 55 ResultSetMetaData rsmd = rs.getMetaData(); 56 for (int i = 1; i <= rsmd.getColumnCount(); ++i) { 57 System.out.print(rsmd.getColumnName(i) + "(" + rsmd.getColumnTypeName(i) + ")\t");//从1开始 58 } 59 System.out.println(); 60 while (rs.next()) { 61 for (int i = 1; i <= rsmd.getColumnCount(); ++i) { 62 System.out.print(rs.getObject(i) + "\t"); 63 } 64 System.out.println(); 65 } 66 } 67 public static void main(String[] args) { 68 69 new Main().test(); 70 71 72 } 73 }
元数据metadata意思是“关于数据的数据”,java中的元数据其实就是注解Annotation
1. 访问和处理Annotation的工具统称APT(Annotation Processiong tool)
2. 4个基本的Annotation: @Override, @Deprecated(过时注解), @Suppress Warings(抑制编译器警告,如转换成泛型的unchecked), @SafeVarargs(java7新增的注解,堆污染注解)
3. 子类重写(其实不能叫重写)父类的静态方法不能用Override注解,因为不属于继承
4. 使用了Deprecated的方法,在编译时会发出警告,在编译时可以使用-Xlint:deprecated可以查看详细的过时代码。使用@Deprecated再生成文档注释时,会自动标注该方法是过时的。
5. 被@SuppressWarning指定的东西会取消显示指定的编译器警告,可以传入一个数组对多个警告类型取消显示
6. 堆污染:把一个不带泛型的变量赋值给一个带泛型的变量可能会引发堆污染
1 public class B { 2 public static void b(List<String> ... listStrArray) { 3 List[] listArray = listStrArray; 4 List<Integer> myList = new ArrayList<Integer>(); 5 myList.add(10); 6 listArray[0] = myList; 7 String a = listStrArray[0].get(0);//注意是用listStrArray赋值 8 } 9 public static void main(String[] args) { 10 //B.b(new ArrayList<String>()); 11 } 12 }
编译、运行这段代码,不管是java6还是java7都没问题。然后把main方法中的注释去掉,分别在java6和java7中编译,发现java6没警告,java7有unchecked警告(查看具体警告用-Xlint:unchecked重新编译)。然后再运行程序,java6和7当然都报错了,但java6显得很突然,因为编译没有,运行却报错了。
6. 在java7以前的jvm不仔细检查泛型的堆污染问题:在定义堆污染方法时没有警告,但是在调用该方法时会有警告。而java7对堆污染定义的方法仔细检查,一编译爆错
7. 如果确保类型是正确的,不会发生堆污染,可以用@SuppressWarings("unchecked")或@SafeVarargs或编译时使用-Xlint:varargs
8. 元Annotion:用来修饰其他的Annotation的Annotation,以下注解的参数默认赋值给value
@Retention,修饰另一个Annotation的生存周期在代码期(RetentionPolicy.SOURCE)还是编译期(RetentionPolicy.CLASS,这是默认值)还是运行期(RetentionPolicy.RUNTIME)
@Target, 修饰另一个Annotation用来修饰的对象是Annotation(ElementType.ANNOTATION_TYPE)、构造器(ElementType.CONSTRUCTOR)、成员变量(ElementType.FIELD)、局部变量(ElementType.LOCAL_VARIABLE)、方法(ElementType.METHOD)、包(ElementType.PACKAGE)、形参(ElementType.PARAMETER)还是类(包括类、接口、枚举定义, ElementType.TYPE),可以修饰多个对象。
@Documented,指定被使用Annotation是否生成到文档注释中。如Test类的info方法使用了@test注解,而@test使用了@Documented注解,那么在查看Test类的info文档时,会发现info上面有一个@test。
@Inherited,比如@test被@Inherited修饰,那么如果B类是A类的子类,A类有@test,那么B类也有@test,如:
import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Inherited //@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME)//别忘了Retention @interface test { } @test class C {} @test public class B extends C { public static void main(String[] args) { System.out.println(B.class.isAnnotationPresent(test.class));//查看是否有该注解 } }
9. 自定义Annotation:
1 import java.lang.annotation.Retention; 2 import java.lang.annotation.RetentionPolicy; 3 4 5 @Retention(RetentionPolicy.RUNTIME) 6 @interface MyAnnotation { 7 String name() default "";//注意是括号,有点像getter和setter结合体 8 int age() default 21; 9 } 10 11 @MyAnnotation(name="lan") 12 public class B { 13 public static void main(String[] args) { 14 15 } 16 }
10. Annotation接口是所有注解实现的接口,实现了java.lang.reflect下的AnnotatedElement接口的类可以获得本类的注解、判断是否存在某个注解。Class、Constructor、Field、Method、Package实现了AnnotatedElement
11. 提取注解信息,这里我模仿JUnit4来做一个MyJUnit:
测试类:
1 import MyJUnit.After; 2 import MyJUnit.Before; 3 import MyJUnit.Test; 4 import static MyJUnit.TestRunner.assertEquals; 5 6 class People { 7 String name; 8 int age; 9 public String getName() { 10 return name; 11 } 12 public void setName(String name) { 13 this.name = name; 14 } 15 public int getAge() { 16 return age; 17 } 18 public void setAge(int age) { 19 this.age = age; 20 } 21 22 } 23 24 public class Main { 25 private People p; 26 @Test 27 public void test1() { 28 System.out.println("执行 test1()"); 29 assertEquals("lan123312", p.getName()); 30 assertEquals(21, p.getAge()); 31 } 32 33 @Before 34 public void before() { 35 System.out.println("执行 before()"); 36 p = new People(); 37 p.setAge(21); 38 p.setName("lan"); 39 } 40 @After 41 public void after() { 42 System.out.println("执行 after()"); 43 p = null; 44 } 45 }
MyJUnit的主类:
1 package MyJUnit; 2 3 import java.lang.reflect.Method; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 public class TestRunner { 8 9 private static int caseNum = 0;//第几个测试用例 10 private static int rightNum = 0;//正确数 11 private static int wrongNum = 0;//错误数 12 private static int exceptionNum = 0;//异常数 13 14 public static void main(String[] args) throws Exception { 15 if (args.length < 1) { 16 System.out.println("编译时请输入要测试的类作为参数(包括包名)! 测试退出"); 17 return; 18 } 19 String classname = args[0]; 20 System.out.println("测试: " + classname); 21 22 Class testClass = Class.forName(classname); 23 Method[] methods = testClass.getDeclaredMethods(); 24 25 List<Method> executeMethods = new ArrayList<Method>();// 测试方法 26 List<Method> afterMethods = new ArrayList<Method>();// after方法 27 28 Object obj = testClass.newInstance();// 获得测试类的示例 29 30 31 for (Method method : methods) { 32 if (method.isAnnotationPresent(Before.class)) { 33 try { 34 method.invoke(obj, new Object[]{});// 先执行before方法 35 } catch (Exception e) { 36 System.out.println("执行before方法: " + classname + "." + 37 method.getName() + " 报错"); 38 e.printStackTrace(); 39 } 40 } else if (method.isAnnotationPresent(Test.class)) { 41 executeMethods.add(method);// 记录test方法 42 } else if (method.isAnnotationPresent(After.class)) { 43 afterMethods.add(method);// 记录after方法 44 } 45 } 46 47 for (Method executeMethod : executeMethods) { 48 try { 49 executeMethod.invoke(obj, new Object[]{});// 执行test方法 50 51 } catch (Exception e) { 52 System.out.println("执行test方法: " + classname + "." + 53 executeMethod.getName() + " 报错"); 54 e.printStackTrace(); 55 exceptionNum++; 56 } 57 } 58 for (Method afterMethod : afterMethods) { 59 try { 60 afterMethod.invoke(obj, new Object[]{});// 执行after方法 61 } catch (Exception e) { 62 System.out.println("执行after方法: " + classname + "." + 63 afterMethod.getName() + " 报错"); 64 e.printStackTrace(); 65 } 66 } 67 System.out.print("正确数:" + rightNum); 68 System.out.print("\t错误数:" + wrongNum); 69 System.out.println("\t异常数:" + exceptionNum); 70 } 71 72 73 public static void assertEquals(Object a, Object b) { 74 boolean flag = false; 75 if (a == b) { 76 flag = true; 77 } else if (a == null) { 78 flag = b.equals(a); 79 } else { 80 flag = a.equals(b); 81 } 82 83 caseNum++; 84 if (flag) { 85 System.out.println("测试用例(" + caseNum + ") 正确"); 86 rightNum++; 87 } else { 88 System.out.println("测试用例(" + caseNum + ") 错误"); 89 wrongNum++; 90 try { 91 throw new WrongException(); 92 } catch (WrongException e) { 93 StackTraceElement[] stack = e.getStackTrace(); 94 System.out.println("错误信息-------------------------"); 95 System.out.println("\t期望: " + a); 96 System.out.println("\t实际: " + b); 97 System.out.println("\n\t文件: \t" + stack[1].getFileName()); 98 System.out.println("\t类:\t" + stack[1].getClassName()); 99 System.out.println("\t方法:\t" + stack[1].getMethodName()); 100 System.out.println("\t行数:\t" + stack[1].getLineNumber()); 101 System.out.println("------------------------------"); 102 } 103 } 104 } 105 }
另外还有4个杂类,这里我把几个文件的代码合在一起:
1 package MyJUnit; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 @Target(ElementType.METHOD) 9 @Retention(RetentionPolicy.RUNTIME) 10 public @interface Test {} 11 12 13 14 package MyJUnit; 15 16 import java.lang.annotation.ElementType; 17 import java.lang.annotation.Retention; 18 import java.lang.annotation.RetentionPolicy; 19 import java.lang.annotation.Target; 20 21 @Target(ElementType.METHOD) 22 @Retention(RetentionPolicy.RUNTIME) 23 public @interface After {} 24 25 26 27 28 package MyJUnit; 29 30 import java.lang.annotation.ElementType; 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 import java.lang.annotation.Target; 34 35 @Target(ElementType.METHOD) 36 @Retention(RetentionPolicy.RUNTIME) 37 public @interface Before {} 38 39 40 41 42 package MyJUnit; 43 44 public class WrongException extends Exception { 45 46 }
编译: javac MyJUnit/*.java -encoding utf-8
javac *.java -encoding utf-8
运行: java MyJUnit/TestRunner Main
注意上面不要写Main.java
也可以同时编译多个包:javac MyJUnit/*.java *.java -encoding utf-8
结果:
12. 可以通过注解来生成一些文件,从而减少代码量、不必编写麻烦的代码。
如:在po/vo中加上注解,然后运行Hibernate/Mybatis的程序,将会生成相应的xml 映射文件,减少程序员的负担
1. File可以使用相对路径,由当前目录决定,当前目录是:
System.out.println(System.getProperty("user.dir"));
2. java的IO流使用了装饰器模式,它将IO流分成节点流(如FileInputStream)和处理流(如PrintStream)
3. 要过滤文件可继承FilenameFilter接口,重写accept方法,使用file.list(FilenameFilter filter)可以获得经过过滤的String[]
4. 流的分类:
按流向来分:输入流、输出流
输入流有两个基类:InputStream(字节输入流)、Reader(字符输入流)
输出流有两个基类:OutputStream(字节输出流)、Writer(字符输出流)
按操作的数据单元分:字节流、字符流
按流的角色分:节点流、处理流(也叫包装流)
节点流直接和物理资源进行交互,处理流包装了节点流
比如FileInpuStream直接跟文件交互,Scanner可以包装FileInputStream,可以更方便的读取文件一行、下一个数字、字符串等
5. 字节流需要一个byte[]作为buffer,字符流需要一个char[]作为buffer
6. 输入流InpuStream和Reader可以用mark(int a)在a位置记录一个标记,用markSupported()看操作系统是否支持mark(),reset()可以回到上一个mark,skip(long n)可以跳过n个字节/字符
7. 调用输出流的close()可以自动flush缓存,不必要调用flush()
8. Windows的换行是\r\n,即使你只输出\n,写入文件也是\r\n,占两字节;linux/Unix的换行是\n
9. 识别处理流的方法:构造器参数是另一个流的就是处理流。
10. System.out的类型是PrintStream。
11. 如果用PrintStream包装OutputStream,那么PrintStream只能输出,不能输入。使用PrintStream很容易输出一行,它可以格式化输出printf。比如用PrintStream包装FileOutputStream,我们调用println("XXX"),就可以很容易在文件中写一行XXX
12. 处理流使用很方便,不用考虑底层buffer和物理资源的交互。关闭处理流时会自动关闭底层节点流。
13. java输入/输出流体系中常用的流分类(下表抄自疯狂java讲义)
分类 | 字节输入流 | 字节输入流 | 字符输入流 | 字符输出流 |
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
14. 如果进行输入/输出的内容是文本,应该用字符流;如果内容是二进制数据,应使用字节流
15. ZipInpuStream/ZipOutputStream和GZIPInputStream/GZIPOutputStream可以压缩/解压文件
16. CipherInputStream/CipherOutputStream可以加密/解密文件
17. AudioInputStream可以访问音频文件
18. 管道流可以用于多进程之间的通信,比如用Runtime.getRuntime().exec()打开一个新的子进程,可以用管道流和子进程交互,Runtime.getRuntime().exec()返回一个进程类Process的对象p,可以用p.getOutputStream(); getInputStream() getErrorStream得到管道流
19. 只有从字节流到字符流的转换流。字节流转到字符流是为了方便,但没必要为了麻烦而从字符流转到字节流。。。
20. 重定向标准输入输出:System.setIn setOut setErr
21. eclipse的输入输出是经过重定向的,而不是直接与控制台交互的
22. 多线程断点下载可以通过RandomAccessFile来实现,下载时先建立一个和下载文件相同大小的文件,再建立一个记录文件指针的文件
23. 序列化就是把对象转换成平台无关的二进制流并持久的保存在磁盘上。如果要学分布式系统如HDFS,最好学学序列化,因为分布式系统需要把序列化的对象传到其他节点使用。序列化机制是java ee平台的基础。
实现序列化需要实现这个两个接口中的其中一个:Serializable、Externalizable
Serializable是一个标记接口,不需要实现任何方法
使用对象流可以对对象进行序列化/反序列化,对象流是个处理流。
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("String1.txt")); String s = "java大法好~"; oos.writeObject(s);//序列化对象,类加载路径下会生成一个String1.txt
oos.close();//别忘了
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("String1.txt")); String ss = (String)ois.readObject();//反序列化对象 System.out.println(s == ss);//返回false
System.out.println(s.equals(ss));//返回true
ois.close();//别忘了
反序列化不经过构造器!反序列化仅仅是在拷贝数据到内存中而已。
如果某个对象已经被序列化,再次调用对象输出流的writeObject方法时,只写入该对象的序列化号。这点很容易理解,如果多个对象A、B、C持有同一个对象D的引用,那么序列化A、B、C后,只序列化一个D对象,在恢复这几个对象时,A、B、C中的D引用才相同,否则引用不同会造成很多问题。
序列化的顺序和反序列化的顺序是相同的!!!!!!!否则不能正常恢复该java对象。
使用了transient修饰的成员属性不会被序列化,在反序列化时,该值是0或null
静态成员不会被序列化
24. 自定义序列化
1 package c15_1; 2 3 import java.io.FileInputStream; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.ObjectInputStream; 7 import java.io.ObjectOutputStream; 8 import java.io.ObjectStreamException; 9 import java.io.Serializable; 10 11 public class Person implements Serializable { 12 13 private static final long serialVersionUID = 1L; 14 15 private String name; 16 private int age; 17 public Person() { 18 System.out.println("无参数的构造器"); 19 } 20 public Person(String name, int age) { 21 System.out.println("有参数的构造器"); 22 this.name = name; 23 this.age = age; 24 } 25 26 27 public String getName() { 28 return name; 29 } 30 public void setName(String name) { 31 this.name = name; 32 } 33 public int getAge() { 34 return age; 35 } 36 public void setAge(int age) { 37 this.age = age; 38 } 39 40 //不管是什么权限,都会被jvm调用。自定义序列化方法 41 private void writeObject(ObjectOutputStream out) throws IOException { 42 System.out.println("调用了writeObject(ObjectOutputStream out) "); 43 out.writeObject(new StringBuffer(name).reverse()); 44 out.writeInt(age); 45 } 46 //不管是什么权限,都会被jvm调用。自定义反序列化方法 47 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 48 System.out.println("调用了readObject(ObjectInputStream in)"); 49 this.name = ((StringBuffer)in.readObject()).reverse().toString(); 50 this.age = in.readInt(); 51 } 52 /** 53 * 当序列化流不完整时,readObjectNoData()方法可以用来正确的初始化反序列化的对象。 54 * 例如,接收方的使用的反序列化类的版本不同于发送方,或者接收方版本扩展的类不是发送方版本 55 * 扩展的类,或者序列化流被篡改时,系统都会调用readObjectNoData方法来初始化反序列 56 * 化的对象。 57 */ 58 private void readObjectNoData() throws ObjectStreamException { 59 System.out.println("readObjectNoData()"); 60 this.name = ""; 61 this.age = 0; 62 } 63 64 public static void main(String[] args) throws Exception { 65 Person p = new Person("lan", 21);//这里调用了一次有参构造器 66 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Person.object")); 67 oos.writeObject(p); 68 oos.close(); 69 70 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.object")); 71 Person p2 = (Person)ois.readObject();//不经过任何构造器 72 System.out.println(p2.getAge() + " " + p2.getName()); 73 ois.close(); 74 } 75 }
默认情况,序列化对象的时候,会调用对象输出流的defaultWriteObject();,而反序列化的时候会调用in.defaultReadObject();
除了上面两个方法,还有一个writeReplace(),在序列化前可以把要序列化的类换成另一个类
/* * 在序列化前会调用这个方法,完全可以将序列化Person编程序列化ArrayList */ private Object writeReplace() throws ObjectStreamException { ArrayList<Object> list = new ArrayList<Object>(); list.add(name); list.add(age); return list; }
这样,在序列化Person时,就会序列化ArrayList,如果ArrayList也写了这个方法来序列化A类,那么就会转而序列化A类......直到某个类没写writeReplace方法为止
由于反序列化不经过构造器,仅仅是拷贝数据到内存,因此会导致反序列化枚举类的时候会出现枚举对象不等于反序列化对象的奇葩现象。
这个问题可以由readResolve()来解决,这个方法与writeReplace()相反。在反序列化时先调用readResolve(), 原先序列化的对象会被抛弃:
public class A implements Serializable { private static final A JAVA = new A(1); private static final A Cgaga = new A(2); private static final A Cgagagaga = new A(3); private int value; private A(int value) { this.value = value; } private Object readResolve() throws ObjectStreamException { System.out.println("执行readResolve()"); if (this.value == 2) { return Cgaga; } else if (this.value == 3) { return Cgagagaga; } else { return JAVA; } } public static void main(String[] args) throws Exception { A java = A.JAVA; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("A.object")); oos.writeObject(java); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("A.object")); A a = (A)ois.readObject();//不经过任何构造器 System.out.println(a == java); ois.close(); } }
如果是enum的话,不会调用readResolve(), jvm自动根据original返回枚举对象,自动保证反序列的对象是枚举对象之一
public enum A implements Serializable { JAVA, Cgaga, Cgagagaga; private Object readResolve() throws ObjectStreamException { System.out.println("执行readResolve()"); if (this.ordinal() == 2) { return Cgaga; } else if (this.ordinal() == 3) { return Cgagagaga; } else { return JAVA; } } public static void main(String[] args) throws Exception { A java = A.JAVA; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("A.object")); oos.writeObject(java); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("A.object")); A a = (A)ois.readObject();//不经过任何构造器 System.out.println(a == java); ois.close(); } }
25. 另一种自定义序列化
实现Externalizable接口,实现两个方法:
public void writeExternal(java.io.ObjectOutput out) throws IOException;该方法调用DataInput(ObjectInput的父接口)的方法来恢复基本类型的Field值,调用ObjectInput的readObject()方法来恢复引用类型的Field值
public void readExternal(java.io.ObjectInput in) throws IOException, ClassNotFoundException;与上面类似,调用DataOutput恢复基本类型数据,调用ObjectOutput的writeObject()方法来恢复引用类型的值
方法体内写的内容请参考上一种自定义序列化。感觉两种方式差不多,只不过Externalizable接口强制自定义序列化
实现Externalizable的性能比实现Serializable的性能要好一些,但是后者方便
26. NIO
java1.4就支持NIO了,说起来不算新了。。。
传统的IO是每次 读取/写入 一个字节/字符 处理的,即面向流的IO系统一次只能处理一个字节,效率不高
NIO采用内存映射文件的方式来处理IO,NIO将文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念)
NIO是面向块的处理
java.nio包: 包含各种与Buffer相关的类
java.nio.channels包: 包含与Channel和Selector相关的类
java.nio.charset包: 包含与字符集相关的类
java.nio.channel.spi包: 包含与Channel相关的的服务提供者编程接口
java.nio.charset.spi包: 包含与字符集相关的服务提供者编程接口
Channel(通道)和Buffer是NIO的两个核心对象。
Channel与传统InputStream和OutputStream的区别是它提供了一个map(),可以将一段数据映射到内存中。
Buffer本质是一个数组,可以使用Channel直接将文件的某块数据映射成Buffer,其实和传统IO的手动使用的数据差不多
NIO还提供了将Unicode字符串映射成字节序列以及拟映射操作的Charset类,也提供了用于支持非阻塞式IO的Selector类
Buffer在创建后容量不可变
Buffer有ByteBuffer、CharBuffer等
ByteBuffer有个子类MappedByteBuffer用于表示Channel将磁盘文件的部分或全部内容映射到内存后得到的结果,又Channel的map方法返回
可以通过Buffer的静态方法allocat(int n);返回一个capacity为n的Buffer,或者通过Channel的map()返回一个MappedByteBuffer
Buffer的get()会让position后移,而get(int index)不会让position后移
0 <= mar <= position <= limit <= capacity
Buffer的flip()把limit设置为position,而position设置为0;clear()把limit设置为capacity(容量),把position设置为0;
mark()设置mark的位置,只能在0到position之间;reset()可以把position设置到mark的位置;
rewind()把position设置为0,取消设置的mark
Channel只能通过传统的InputStream、OutputStream的getChannel得到
1 public class FileChannelTest { 2 static void writeFile() throws Exception { 3 FileOutputStream out = new FileOutputStream("1.txt"); 4 PrintStream ps = new PrintStream(out); 5 ps.println("java大法好"); 6 ps.close(); 7 } 8 public static void main(String[] args) throws Exception { 9 writeFile();//生成1.txt 10 11 File file = new File("1.txt"); 12 try (//自动关闭资源 13 FileInputStream in = new FileInputStream(file); 14 FileChannel inChannel = in.getChannel(); 15 FileChannel outChannel = new FileOutputStream("2.txt").getChannel(); 16 ) { 17 //读取模式为只读,还可以设置为MapMode.READ_WRITE等 18 //buffer的position设置为0,capacity设置为file的大小 19 MappedByteBuffer buffer = inChannel.map(MapMode.READ_ONLY, 0, file.length()); 20 outChannel.write(buffer); 21 22 buffer.clear();//记得把position置0 23 24 //以下内容把MappedByteBuffer转成CharBuffer 25 //使用Charset设置编码,用CharsetDecoder解码成CharBuffer 26 //很好理解,Byte变成Char要设定字符集才能知道字节数据表示什么字 27 Charset charset = Charset.forName("UTF-8"); 28 CharsetDecoder decoder = charset.newDecoder(); 29 CharBuffer charBuffer = decoder.decode(buffer);//也可用charset.decode(buffer); 30 System.out.println(charBuffer); 31 } 32 } 33 }
跟Input挂钩的Channel只能read不能write,跟Output挂钩的Channel只能write不能read
RandomAccessFile得到的Channel是读还是写要看生成RandomAccessFile时设置的读写模式,另外还能通过position(long n)设置从哪儿开始写
使用NIO的Channel和Buffer,我们仍然可以使用传统IO的“多次重复取水”的方式操作流
比如
while( inChannel.read(buffer) != -1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); }
27. 字符集和Charset
NIO有几个与字符相关的类:CharEncoder、CharDecorder、Charset。在上面已经演示了CharDecoder和Charset,CharEncoder与CharDecorder用法差不多,都是由Charset.nextXxx()得到。主要作用就是把ByteBuffer转换成CharBuffer。另外charset.availableCharsets()返回SortedMap<String, Charset>,通过遍历这个Map可以知道电脑支持的字符集。
28. 文件锁
FileChannel提供lock()和tryLock()可以获得文件锁FileLock对戏那个。lock()如果得不到文件锁会阻塞,而tryLock()得不到文件锁只会返回null 。
lock(long position, long size, boolean shared); 将从position开始的size个字节锁住,禁止写。shared为true时,表明该锁该锁是一个共享锁,它允许多个进程来读取该文件,但阻止其他进程获得对该文件的排它锁。当shared为false时,表明该锁是一个排它锁,它将锁住对文件的读和写。可以使用lock.isShared()判断是否是共享锁。
可以对文件的某个部分加锁锁。释放锁用lock.release()。
文件锁虽然可以用于控制并发访问,但于对高并发访问的清醒,还是推荐使用数据库来保存程序信息,而不是使用文件。
在某些平台上,文件所仅仅是建议性的,并不是强制性的。这意味着即使一个程序不能获得文件锁,它也可以对该文件进行读写。
在某些平台上,不能同步地锁定一个文件并把它映射到内存中。
文件锁是由Java虚拟机所持有的,如果两个java程序使用同一个java虚拟机运行,则它们不能对同一个文件进行加锁
在某些平台上关闭FileChannel时,会释放Java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel
29. java7的NIO.2
File类功能有限,它不能利用特定文件系统的特性,File所提供的方法的性能也不高。而且大多数方法在出错时仅返回失败,并不会提供异常信息。
NIO.2为了弥补这种不足,引入了一个Path接口,Path接口代表一个平台无关的平台路径。
除此之外,NIO.2还提供了Files和Paths两个工具类,其中Files包括了大量静态工具方法操作文件。
Paths则包含了两个返回Path的静态工厂方法:Paths.get(String uri);, Paths.get(String a, String b, String c);(路径是a:\b\c)
Paths工具类:
1 public class PathsTest { 2 public static void main(String[] args) throws Exception { 3 Path path = Paths.get(".");// 当前路径/. 4 System.out.println("path中的路径数: " + path.getNameCount()); 5 Path absolutePath = path.toAbsolutePath();//转换成绝对路径 6 System.out.println(absolutePath); 7 8 System.out.println(absolutePath.getRoot());//获得磁盘根路径 9 10 System.out.println(absolutePath.getNameCount());//当前目录下的路径数 11 12 Path path2 = Paths.get("c:", "Windows", "System");//拼接目录 13 System.out.println(path2); 14 } 15 }
Files工具类:
1 public class FilesTest { 2 public static void main(String[] args) throws Exception { 3 //复制文件一句话就搞定 4 Files.copy(Paths.get("c:/1.txt"), new FileOutputStream("c:/2.txt")); 5 System.out.println(Files.isHidden(Paths.get("c:/1.txt")));//是否隐藏 6 //读取文件的所有行,每行一个String 7 List<String> lines = Files.readAllLines(Paths.get("c:/1.txt"), Charset.forName("UTF-8")); 8 9 lines.add("java大法好"); 10 lines.add("java大法好"); 11 lines.add("java大法好"); 12 //增加几行后输出 13 Files.write(Paths.get("c:/2.txt"), lines, Charset.forName("UTF-8")); 14 15 FileStore store = Files.getFileStore(Paths.get("C:")); 16 System.out.println("c盘总空间: " + store.getTotalSpace() + " c盘可用空间:" + store.getUsableSpace()); 17 } 18 }
掌握Files类可以方便的复制、读写文件,减少代码量
FileVisitor递归遍历文件夹:
1 public class FileVisitorTest { 2 public static void main(String[] args) throws Exception { 3 Files.walkFileTree(Paths.get("."), 4 new SimpleFileVisitor<Path>() { 5 int depth = 0; 6 @Override 7 public FileVisitResult visitFile(Path file , 8 BasicFileAttributes attr) throws IOException { 9 for (int i = 0; i < depth; ++i) { 10 System.out.print("\t"); 11 } 12 System.out.println("正在访问" + file + "文件"); 13 return FileVisitResult.CONTINUE; 14 } 15 @Override 16 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) throws IOException { 17 for (int i = 0; i < depth; ++i) { 18 System.out.print("\t"); 19 } 20 System.out.println("|------正在访问" + dir + "文件夹"); 21 depth++; 22 return FileVisitResult.CONTINUE; 23 } 24 @Override//访问文件夹完毕后调用此方法 25 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 26 depth--; 27 28 return FileVisitResult.CONTINUE; 29 } 30 31 }); 32 } 33 }
监听路径下的变化:
1 public class WatchServiceTest { 2 3 public static void main(String[] args) throws Exception { 4 WatchService watchService = FileSystems.getDefault().newWatchService(); 5 //注意要有/,否则路径不正确 6 //只能监听c盘一级子文件/文件夹的变化 7 Paths.get("C:/").register(watchService, 8 StandardWatchEventKinds.ENTRY_CREATE, 9 StandardWatchEventKinds.ENTRY_MODIFY, 10 StandardWatchEventKinds.ENTRY_DELETE); 11 while (true) { 12 WatchKey key = watchService.take(); 13 for (WatchEvent<?> event : key.pollEvents()) { 14 System.out.println(event.context() + "文件发生了" + event.kind() + "事件"); 15 } 16 boolean valid = key.reset(); 17 if (!valid) { 18 break; 19 } 20 } 21 } 22 23 }
要处理文件读取失败,可以用FileVisitResult visitFileFailed(T file, IOException exec);
FileVisitResult的枚举对象:CONTINUE(继续访问)、SKIP_SIBLINGS(继续访问,但不访问同级文件)、SKIP_SUBTREE(继续访问,但不访问子文件)、TERMINATE(停止访问)
查看/修改文件属性:
java.nio.file.aattribute下提供大量的文件属性工具类
通过XxxAttributeView的getAttributes()获得XxxAttributes类,修改这个类就能修改文件属性
FileAttributeView是其他XxxAttributeView的父接口
AclFileAttributeView可以为特定文件设置ACL(access control list)及文件所有者属性。它的getAcl()返回List<AclEntry>对象,该返回值代表了该文件的权限集。通过setAcl(List)方法可以修改ACL
BasicFileAttributeView可以获取或修改文件的基本属性,如最后修改时间、最后访问时间、创建时间、大小、是否为目录、是否为符号链接等。它的readAttributes()返回一个BasicFileAttributes对戏那个。修改文件属性是通过修改BasicFileAttributes完成的
DosFileAttributeView:文件是否只读、是否隐藏、是否为系统文件、是否是存档文件等,也是通过getAttributes()来修改的
PosixFileAttributeView:可以修改文件所有者、组所有者、访问权限信息(在UNIX、LINUX上使用)
1 public class AttributeViewTest { 2 3 public static void main(String[] args) throws Exception { 4 Path testPath = Paths.get("c:/1.txt"); 5 BasicFileAttributeView basicView = Files.getFileAttributeView( 6 testPath, BasicFileAttributeView.class); 7 BasicFileAttributes basicAttrs = basicView.readAttributes(); 8 System.out.println("创建时间: " + new Date(basicAttrs.creationTime().toMillis())); 9 System.out.println("最后访问时间: " + new Date(basicAttrs.lastAccessTime().toMillis())); 10 System.out.println("最后修改时间: " + new Date(basicAttrs.lastModifiedTime().toMillis())); 11 System.out.println("文件大小: " + basicAttrs.size()); 12 13 FileOwnerAttributeView ownerView = Files.getFileAttributeView( 14 testPath, FileOwnerAttributeView.class); 15 System.out.println("文件所属的用户: " + ownerView.getOwner()); 16 //获取guest对应的用户 17 UserPrincipal user = FileSystems.getDefault() 18 .getUserPrincipalLookupService() 19 .lookupPrincipalByName("guest"); 20 //修改c:/1.txt的用户是guest 21 ownerView.setOwner(user); 22 23 24 UserDefinedFileAttributeView userView = Files.getFileAttributeView( 25 testPath, UserDefinedFileAttributeView.class); 26 List<String> attrNames = userView.list();//获取1.txt的所有自定义属性 27 for (String name : attrNames) { 28 ByteBuffer buf = ByteBuffer.allocate(userView.size(name)); 29 userView.read(name, buf); 30 buf.flip(); 31 String value = Charset.defaultCharset().decode(buf).toString(); 32 System.out.println(name + "--------->" + value); 33 } 34 userView.write("发行者", Charset.defaultCharset().encode("java大法好")); 35 36 DosFileAttributeView dosView = Files.getFileAttributeView(testPath, 37 DosFileAttributeView.class); 38 39 dosView.setHidden(true); 40 dosView.setReadOnly(true); 41 42 } 43 44 }
1. 线程不拥有系统资源
2. 线程共享的环境:进程代码段、进程的公有数据等
3. 两种创建线程的方式:
1) A类继承Thread,这种方式可以比较方便的使用this来调用A类的成员变量
2) A类实现Runnable,然后设置Thread的target为A。如果要使用A类中的成员变量,需要用Thread.currentThread();
3) Callable和Future创建线程,设置Thread的Target为Callable。也要使用Thread.currentThread()来使用Callable的成员变量
4. 使用方式2)和方式3)创建多个线程,让它们的target指向同一个Runnable或Callable实现类,这样就能在多个线程中共享Runnable或Callable对象中的成员变量
5. 方式3)比前两者要强大,因为它的线程执行体call()可以有返回值并可以抛出异常
6. 方式3)示例:
1 public class CallableTest implements Callable<Integer> { 2 @Override 3 public Integer call() throws Exception { 4 int i = 0; 5 while (i < 100) { 6 System.out.println(Thread.currentThread().getName() + "------>" + i++); 7 } 8 return i; 9 } 10 public static void main(String[] args) { 11 CallableTest call = new CallableTest(); 12 //Future接口的实现类:FutureTask 13 FutureTask<Integer> task = new FutureTask<Integer>(call); 14 //Future可以取消Callable任务,可以使用isCancelled()看是否取消任务,使用isDone()看任务是否完成 15 for (int i = 0; i < 100; ++i) { 16 System.out.println(Thread.currentThread().getName() + "------->" + i); 17 if (i == 20) { 18 new Thread(task, "有返回值的线程").start(); 19 } 20 } 21 try { 22 //get方法会导致线程阻塞,通过get(long timeout, TimeUnit unit)设置超时时间,要catch超时异常 23 24 System.out.println("子线程的返回值:" + task.get()); 25 } catch(Exception e) { 26 27 } 28 } 29 }
7. 使用了方式2)和方式3)可以继承其他类,使用方式1)不能继承其他类了
8. 线程状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)
9. 永远不要直接调用run()或call()
10. start()线程后不一定会马上执行线程,取决于jvm的调度
11.只能对处于新建状态(没start过的)的线程调用start(),否则将引发IllegalThreadStateException
12. 线程一旦sleep,jvm就会让其他线程执行,因此在小程序中,主线程sleep后,守护线程--gc比较可能会回收垃圾
13. Thread的suspend()方法将因此线程挂起。但这个方法容易导致死锁,尽量避免使用此方法。stop()方也也容易死锁,尽量别用。
14. 当前线程被阻塞,其他线程就会获得执行机会
15. 处于挂起状态的线程可以使用resume()恢复到就绪状态
16. 本来线程是占用cpu资源的,但是如果挂起的话,操作系统就不给这个现成分配cpu资源,除非以后再恢复,所以线程挂起的作用就是节省cpu资源(摘自百度知道)
17. join()可以让其他线程执行完毕后再执行当前线程。如:在main方法中调用thread.join(),那么主线程将会等待直到thread线程执行完毕
18. 设置守护线程(后台线程/精灵线程):thread.setDaemon(true);。当前台线程全都挂了,那么守护线程也会挂,最后关闭jvm,gc就是一个守护线程。前台线程的子线程默认是前台线程,后台线程的子线程默认是后台线程。前台线程都挂了之后,jvm会通知后台线程去死,需要一定时间,需要在start之前设置daemon,否则会引发IllegalThreadStateException
19. 线程让步yield:让本线程回到就绪状态,重新让jvm按优先级调度线程,因此可能会出现高优先级的线程调用yield()后还是该线程执行,这点与sleep不一样
20. 使用setPriority(int 1~10)设置优先级,Thread.MAX_PRIORITY=10, Thread.MIN_PRIORITY=1, Thread.NORM_PRIORITY = 5。虽然jvm提供10个优先级,但还是要看操作系统提供了几个优先级,比如win 2007提供了7个优先级,因此应该避免直接使用数字设置优先级,而是使用上面三个静态变量设置优先级
21. 主线程和子线程的地位是一致的,因此主线程死了之后子线程依然可以继续运行
22. synchronized方法的同步监听器是this,一个进程进入A类的对象a的synchronized方法a()还没返回的时候,另一个线程不可以进入到a对象的任意一个synchronized,因为a对象被锁住了
23. 在同步方法、同步块中抛出异常时会释放锁,wait方法会释放锁。sleep、yield、suspend、resume方法不会释放锁
24. 同步锁。
Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock、ReadWriteLock、是java5提供了两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供ReentrantReadWrite(可重入读写锁)
1 package c16_2; 2 3 import java.util.concurrent.locks.ReentrantLock; 4 5 class A extends Thread { 6 int value; 7 LockTest test; 8 public A(int value, LockTest test) { 9 this.value = value; 10 this.test = test; 11 } 12 public void run() { 13 if (value == 1) { 14 test.m(); 15 } else { 16 test.n(); 17 } 18 } 19 } 20 21 public class LockTest { 22 private final ReentrantLock lock = new ReentrantLock(); 23 public void m() { 24 lock.lock();//开始锁定 25 System.out.println("lock m() 10s"); 26 try { 27 28 Thread.currentThread().sleep(10000); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } finally { 32 lock.unlock();//解锁 33 } 34 } 35 public void n() { 36 lock.lock(); 37 System.out.println("lock n() 10s"); 38 try { 39 Thread.currentThread().sleep(10000); 40 } catch (InterruptedException e) { 41 e.printStackTrace(); 42 } finally { 43 lock.unlock(); 44 } 45 } 46 public static void main(String[] args) { 47 LockTest test = new LockTest(); 48 A a1 = new A(1, test); 49 A a2 = new A(2, test); 50 a1.start(); 51 a2.start(); 52 } 53 }
运行上面的代码可以发现,Lock是锁住一个对象的,不容许其他线程进入这个对象的任意一个Lock块
25. 死锁
1 class A1 { 2 public synchronized void foo(B1 b) { 3 System.out.println("当前线程名" + Thread.currentThread().getName() + "进入A1实例的foo方法"); 4 5 try { 6 Thread.sleep(200); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 System.out.println("当前线程名" + Thread.currentThread().getName() + "调用B1实例的last方法"); 11 b.last(); 12 } 13 public synchronized void last() { 14 System.out.println("进入A1实例的last方法内部"); 15 } 16 } 17 18 class B1 { 19 public synchronized void bar(A1 a) { 20 System.out.println("当前线程名" + Thread.currentThread().getName() + "进入B1实例的bar方法"); 21 try { 22 Thread.sleep(200); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 System.out.println("当前线程名" + Thread.currentThread().getName() + "调用A1实例的last方法"); 27 a.last(); 28 } 29 public synchronized void last() { 30 System.out.println("进入B1实例的last方法内部"); 31 } 32 } 33 34 public class DeadLock implements Runnable { 35 A1 a = new A1(); 36 B1 b = new B1(); 37 38 public void init() { 39 Thread.currentThread().setName("主线程"); 40 a.foo(b); 41 System.out.println("进入了主线程之后"); 42 } 43 public void run() { 44 Thread.currentThread().setName("副线程"); 45 b.bar(a); 46 System.out.println("进入了副线程之后"); 47 } 48 public static void main(String[] args) { 49 DeadLock dl = new DeadLock(); 50 new Thread(dl).start(); 51 dl.init(); 52 } 53 }
进入a.foo()时会锁上a对象,然后在foo()中调用b.bar(),这时b对象也会被锁,接着在bar()方法中调用a.last()。由于之前a已经被锁了,因此此时调用a.last()会发生死锁,b对象等待a.foo(),而a对象等待b.bar(),两者互相等待,一直等到永远~
26. 传统的线程通信: wait()、notify()、notifyAll()
27. 如果A对象有两个同步方法 a1()、a2(),需要用一个线程去调用a1(),另一个线程调用a2(),而且要严格交替运行a1()和a2(),那么可以用flag+wait()+notifyAll()来做到
28. notify只能随机唤醒其中一个已经wait的线程,而notifyAll能换全部wait线程。比如A的对象a有很多个同步方法,只要调用notifyAll,那么在a对象的同步方法中睡着的所有线程全部被唤醒。在A类的其他对象的方法中睡着的线程是不会被唤醒的,因为是按同步监听器来唤醒
29. 在a对象的同步方法a()中wait()之后,线程阻塞,但是此时释放锁了,因此其他线程能进入a的任意同步方法,包括a()。
下面的程序将会出现进入两次a()方法的情况,说明wait()会释放this锁
1 class AA extends Thread { 2 WaitTest test; 3 public AA(WaitTest test) { 4 this.test = test; 5 } 6 public void run() { 7 test.a(); 8 } 9 } 10 11 public class WaitTest { 12 public synchronized void a() { 13 System.out.println("进入a方法"); 14 try { 15 this.wait(); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 public static void main(String[] args) { 21 WaitTest test = new WaitTest(); 22 new AA(test).start(); 23 new AA(test).start(); 24 } 25 }
30. 如果使用Lock来同步,那么不存在隐式的同步监听器,也就不能使用wati()、notify()、nofityAll()来进行线程通信了。使用Lock需要用Condition来控制线程通信。Lock代替了同步方法或同步代码块,Condition代替了同步监视器的功能。Condition需要绑定Lock,await()方法类似于wait(),signal()类似与notify(),signalAll()类似于notifyAll()。通过lock.newCondition()可以获得Condition
31. 使用阻塞队列(BlockingQueue)控制线程通信
阻塞队列是有泛型参数的,代表队列中元素的类型
BlockingQueue接口是Queue的子接口。它有一个重要的特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程视图从BlokingQueue中取出元素时,如果该队列已空,则该线程被阻塞。阻塞队列长度不可变。
在队列尾部插入元素,包括add、offer、put方法,当该队列已满时,这3个方法分别会抛出异常、返回false、阻塞队列
在队列头部删除并返回删除元素。包括remove、poll、take方法,当该队列已空时,这3个方法分别会抛出异常、返回false
在队列头部获取但不删除元素。包括element、peek方法,当队列已空时,抛出异常、返回false
ArrayBlokingQueue:数组阻塞队列
LinkedBlokingQueue:链表阻塞队列
PriorityBlockingQueue:优先阻塞队列,通过实现Comparable接口来比较
SynchronousQueue:同步队列,队列的存取必须交替进行
32. 线程组ThreadGroup
对线程组的控制相当于同时控制这批线程。如果程序没有显式指定线程属于哪个线程组,则该线程组属于默认线程组。在默认情况下,子线程和创建它的父线程处于同一个线程组内。一旦某个线程加入了指定线程组之后,该线程将一直属于该线程组,直到该线程死亡,线程运行中途不能改变它所属的线程组,因此Thread类没有提供setThreadGroup()来改变所属的线程组但是能用getter。
ThreadGroup类提供了几个常用方法:
int activeCount() :线程组中活动线程的数目
interrupt():中断线程组中的所有线程
isDaemon():是否是后台线程组
setDaemon(boolean daemon):设置是否后台线程组。当后台线程组的最后一个线程执行结束或最后一个线程被销毁后,后台线程组将自动销毁
setMaxPriority(int pri): 设置最高优先级
getName()
uncaughtException(Thread t, Throwable e):可以处理该线程组内的任意线程所抛出的未处理异常。
如果线程执行过程中抛出了一个未处理异常,JVM在结束该线程之前会自动查找时候有对应的Thread.UncaughtExceptionHandler对象,如果找到该处理器对象,则会调用该对象的uncaughtException方法来处理该异常。Thread.UncaughtExeptionHandler是Thread的静态内部接口,只有uncaunghtException方法。
Thread提供两个方法来设置处理器对象:
static setDefaultUncaughtExceptionHandler(Thread.UncaughtExeptionHandler eh);
setUncaughtExeptionHandler(Thread.UncaughtExeptionHandler eh);
ThreadGroup实现了Thread.UncaughtExeptionHandler,所以每个线程所属的线程组将会作为默认的异常处理器。
如果Thread没设置异常处理器,则调用这个线程所属的线程组的uncaunghtException方法
线程组异常处理器的默认流程:
如果该线程组有父线程组,则调用父线程组的uncaughtExeption
如果该线程示例所属的线程类有默认的异常处理器,则调用
如果该对戏那个是ThreadDeath的对象,则不做任何处理。否则将异常跟踪栈的信息打印到System.err错误输出流,并结束该线程
使用Thread.UncaughtExeptionHandler处理异常后依然会throws异常给上一层调用者
33. 线程池
java5以前,开发者必须手动实现自己的线程池,java5开始,java内建支持线程池。java5新增了一个Executors工厂类来生产线程池。该工厂类包含如下几个静态工厂方法来创建线程池:
newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中
newFixedThreadPool(int nThreads): 创建一个可重用的、具有固定线程数的线程池
newSingleThreadExecutor():创建一个只有单线程的线程池,相当于newFixedThreadPool(1)
newScheduledThreadPool(int corePoolSize):创建具有指定线程数的此案城池,它可以在指定延迟后执行线程任务。corePoolSize是池中所保存的线程数,即使线程是空闲的也被保存在线程池内。
newSingleTHreadScheduledThreadPool():相等上面两个的结合
上面5个方法中前三个返回一个ExecutorService对象,代表一个线程池。后两个方法返回一个ScheduledExecutorService线程池,她是ExecutorService的子类,可以执行延迟后执行线程任务。
ExecutorService通过submit方法提交Runnable或Callable任务,在有空闲线程时至此那个任务
ScheduledExecutorService通过schedule方法提交Runnable或Callable任务
submit方法返回Future<T>,但是Runnable的run方法没返回值,因此sumit一个Runnable时返回null
schedule方法返回ScheduledFutrure<V>,跟Future差不多
使用玩线程池后,用shutdown()来关闭线程池,线程池会先把池里的所有线程任务先执行完毕(包括等待任务),不接收新任务,然后才释放池子资源
shutdownNow()也可以关闭线程池,它试图停止正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表
class MyThread implements Runnable { public void run() { for (int i = 0; i < 100; ++i) { System.out.println(Thread.currentThread().getName() + "的i值为" + i); } } } public class ThreadPoolTest { public static void main(String[] args) throws Exception { ExecutorService pool = Executors.newFixedThreadPool(6);//获得池子 pool.submit(new MyThread());//提交任务,因为有6个空闲任务,所以立即执行 pool.submit(new MyThread()); pool.shutdown();//关闭池子 } }
34. java7提供的ForkJoinPool
顾名思义,这是一个“并行池”,与并发不一样,并行池可以在同一时刻使用多个cpu对小任务进行并行计算,然后将结果合并起来。这很像MapReduce。
因为是并行计算,因此能够很好的利用多核cpu的资源。
构造器:
ForkJoinPool(int parallelism):创建一个包含parallelism个并行线程的ForkJoinPool
ForkJoinPool():以Runtime.availableProcessors()的返回值作为parallelism来创建ForkJoinPool
通过submit(ForkJoinTask task)/invoke(ForkJoinTask task)来执行任务
ForkJoinTask代表一个可以并行、合并的任务,是一个抽象类,继承了Future,。它还有两个抽象子类:RecursiveAction和RecursiveTask,前者没返回值,后者有返回值。
继承关系:
Executor -> ExecutorService -> AbstractExecutorService -> ForkJoinPool
无返回值:
1 class PrintTask extends RecursiveAction { 2 private static final int THRESHOLD= 50; 3 private int start; 4 private int end; 5 public PrintTask(int start, int end) { 6 this.start = start; 7 this.end = end; 8 } 9 10 @Override 11 protected void compute() { 12 // TODO Auto-generated method stub 13 if (end - start < THRESHOLD) { 14 System.out.println("############### " + (end-start)); 15 for (int i = start; i < end; ++i) { 16 System.out.println(Thread.currentThread().getName() + "的i值:" + i); 17 } 18 } else { 19 int middle = (start + end) / 2; 20 PrintTask left = new PrintTask(start, middle); 21 PrintTask right = new PrintTask(middle, end); 22 left.fork(); 23 right.fork(); 24 } 25 } 26 27 } 28 29 public class ForkJoinPoolTest { 30 public static void main(String[] args) { 31 /* 32 * 因为我的电脑是4核的,因此池里默认只有同时执行4个RecursiveAction 33 * 即使在PrintTask中被分成成8个对象执行fork(),单实际只有有4个同时 34 * 在调用,等其中某个对象任务执行完毕后,再加到池里运行 35 */ 36 ForkJoinPool pool = new ForkJoinPool(); 37 pool.submit(new PrintTask(0, 300)); 38 try { 39 pool.awaitTermination(2, TimeUnit.SECONDS); 40 } catch (InterruptedException e) { 41 e.printStackTrace(); 42 } finally { 43 pool.shutdown(); 44 } 45 } 46 47 }
有返回值:
class CalTask extends RecursiveTask<Integer> { private static final int THRESHOLD = 20; private int arr[]; private int start; private int end; public CalTask(int[] arr, int start, int end) { super(); this.arr = arr; this.start = start; this.end = end; } @Override protected Integer compute() { int sum = 0; if (end - start < THRESHOLD) { for (int i = start; i < end; ++i) { sum += arr[i]; } return sum; } else { int middle = (start + end) / 2; CalTask left = new CalTask(arr, start, middle); CalTask right = new CalTask(arr, middle, end); left.fork(); right.fork(); //这个不是线程里的join而是返回left的compute方法的返回值 //加上right的compute方法的返回值 return left.join() + right.join(); } } } public class Sum { public static void main(String[] args) throws Exception { int [] arr = new int[100]; Random rand = new Random(); int total = 0; for (int i = 0, len = arr.length; i < len; ++i) { int tmp = rand.nextInt(20); total += (arr[i] = tmp); } System.out.println(total); ForkJoinPool pool = new ForkJoinPool(); Future<Integer> future = pool.submit(new CalTask(arr, 0, arr.length));//获得返回值 System.out.println(future.get().equals(total)); pool.shutdown(); } }
35. ThreadLocal类代表一个线程局部变量,通过把数据放在ThreadLocal中就可以让每个线程创建一个该变量的副本,从而避免并发访问的线程安全问题。
ThreadLocal不能替代同步机制,,两者面向的问题领域不同。同步机制是为了同步多个线程对相同数据的并发访问。而ThreadLocal是为了隔离多个线程进行线程的数据共享,从根本上避免多个线程之间对共享资源的竞争,也就不需要同步了。
如果要共享资源就用同步资源,如果要隔离多个线程之间的共享冲突,就用ThreadLocal
ThreadLocal的三个方法:set(T t)、get()、remove()
1 class Account { 2 private ThreadLocal<String> name = new ThreadLocal<>(); 3 public Account(String str) { 4 this.name.set(str);//set方法 5 System.out.println("---" + this.name.get());//get方法 6 } 7 public String getName() { 8 return this.name.get(); 9 } 10 public void setName(String str) { 11 this.name.set(str); 12 } 13 } 14 15 class MyTest extends Thread { 16 private Account account; 17 public MyTest(Account account, String name) { 18 super(name); 19 this.account = account; 20 } 21 public void run() { 22 for (int i = 0; i < 10; ++i) { 23 if (i == 6) { 24 account.setName(getName()); 25 } 26 System.out.println(account.getName() + " 账户的i:" + i); 27 } 28 } 29 } 30 31 public class ThreadLocalTest { 32 public static void main(String[] args) { 33 Account at = new Account("初始名"); 34 new MyTest(at, "线程甲").start();//虽然两个线程,但是at中的name提供给每个线程setname的值的拷贝,因此不会发生冲突 35 new MyTest(at, "线程乙").start(); 36 } 37 38 }
36. 以Concurrent开头的集合类代表了支持并发访问的集合,他们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但读取操作操作不必锁定。以Concurrent开头的集合采用了更复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。
当多个线程共享访问一个公共集合时,ConcurrentLinkedQueue是一个恰当选择,ConcurrentLinkedQueue不允许使用null元素。ConcurrentLinkedQueue实现了多线程的高效访问,多个线程访问它无需等待。
在默认情况下,ConcurrentHashMap支持16个线程并发写入,当有超过16个线程并发向该Map中写入数据时,可能有些线程需要等待。实际上,程序通过设置concurrencyLevel构造参数来支持更多的并发写入线程
前面提到的两个集合得到迭代器后修改集合,不会抛出异常,但是迭代器不能反映变化
CopyOnWriteArraySet的底层封装了CopyOnWriteArrayList
对于CopyOnWriteArrayList,采用赋值数组的方式来实现写操作。读取时只须读取集合本身,而修改操作会让该集合生成新的数组,所有的修改操作都在副本上进行,因此是线程安全的。频繁修改时效率很差,但由于读操作和修改操作不是操作同一个数组,而且加操作也不需要加锁,因此度操作就很快、很安全。因此CopyOnWriteArrayList适合用在读取远远大于修改的场景,比如缓存。
1. 公认端口:0~1023 注册端口:1024~49151,一般用这个 动态/私有端口: 49152~65535 应用程序一般按不会主动使用这些端口
2. InetAddress代表IP地址,用getByName和getByAddress获得实例,Name比如www.baidu.com,而Adreess是ip。InetAddress有两个子类:Inet4Address、Inet6Address分别代表ipv4和ipv6
3. 使用URLDecoder.decode()可以把带“%xx”url的字符串按指定搁置转换成普通的字符串(可以有中文),而URLEncoder.encoder()可以把普通url字符串转成带"%xx"的url
4. URI代表统一资源标识符,它不能定位资源,唯一的作用是解析。URL是特殊的URI,URL可以定位网络资源,并得到资源的流,从而下载资源
多线程下载:
1 package c17_2; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.RandomAccessFile; 6 import java.net.HttpURLConnection; 7 import java.net.URL; 8 import java.util.Scanner; 9 10 public class DownUtil { 11 private String path; 12 private String targetFile; 13 private int threadNum; 14 private DownThread[] threads; 15 private int fileSize; 16 17 public DownUtil(String path, String targetFile, int threadNum) { 18 this.path = path; 19 this.threadNum = threadNum; 20 this.targetFile = targetFile; 21 threads = new DownThread[threadNum]; 22 } 23 24 public void download() throws Exception { 25 URL url = new URL(path); 26 HttpURLConnection conn = (HttpURLConnection)url.openConnection(); 27 setHeadInfo(conn); 28 29 fileSize = conn.getContentLength();//第一次连接仅读取网络资源的大小就关闭了 30 conn.disconnect();//关闭连接 31 32 int currentPartSize = fileSize / threadNum + 1;//触发会去掉小数点,+1会保证能把文件读完 33 RandomAccessFile file = new RandomAccessFile(targetFile, "rw"); 34 file.setLength(fileSize);//生成文件 35 file.close(); 36 37 for (int i = 0; i < threadNum; ++i) { 38 int startPos = i * currentPartSize; 39 RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw"); 40 currentPart.seek(startPos); 41 threads[i] = new DownThread(startPos, currentPartSize, currentPart); 42 threads[i].start(); 43 } 44 45 } 46 private void setHeadInfo(HttpURLConnection conn) throws Exception { 47 conn.setConnectTimeout(5 * 1000);//5秒超时 48 conn.setRequestMethod("GET");//常用的get和post 49 conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg," 50 + "application/x-shockwave-flash, application/xaml+xml, " 51 + "application/vnd.ms-xpsdocument, application/vnd.ms-xbap," 52 + "application/x-ms-application, application/vnd.ms-excel," 53 + "application/vnd.ms-powerpoint, application/msword, */*");//设置允许的格式 54 conn.setRequestProperty("Accept-Language", "zh-CN");//设置语言 55 conn.setRequestProperty("Charset", "UTF-8");//设置编码格式 56 57 } 58 public double getCompleteRate() { 59 int sumSize = 0; 60 for (int i = 0; i < threadNum; ++i) { 61 sumSize += threads[i].length; 62 } 63 return sumSize*1.0 / fileSize; 64 } 65 public class DownThread extends Thread {//内部线程类 66 private int startPos; 67 private int currentPartSize; 68 private RandomAccessFile currentPart; 69 public int length; 70 public DownThread(int startPos, int currentPartSize, RandomAccessFile currentPart) { 71 this.startPos = startPos; 72 this.currentPart = currentPart; 73 this.currentPartSize = currentPartSize; 74 } 75 76 public void run() { 77 InputStream in = null; 78 try { 79 URL url = new URL(path); 80 HttpURLConnection conn = (HttpURLConnection)url.openConnection();//获得connection 81 DownUtil.this.setHeadInfo(conn); 82 in = conn.getInputStream();//获得输入流,可以下载东西 83 in.skip(this.startPos);//跳过不需要读的段 84 byte[] buffer = new byte[1024]; 85 int hasRead = 0; 86 while (length < currentPartSize && (hasRead = in.read(buffer)) != -1) { 87 currentPart.write(buffer, 0, hasRead); 88 length += hasRead; 89 } 90 91 } catch(Exception e) { 92 e.printStackTrace(); 93 } finally {//正确关闭流 94 if (currentPart != null) { 95 try { 96 currentPart.close(); 97 } catch (IOException e) { 98 e.printStackTrace(); 99 } 100 } 101 if (in != null) { 102 try { 103 in.close(); 104 } catch (IOException e) { 105 e.printStackTrace(); 106 } 107 } 108 } 109 } 110 } 111 112 public static void main(String[] args) throws Exception { 113 // System.out.println("请输入下载地址: "); 114 // Scanner scan = new Scanner(System.in); 115 // String path = scan.next(); 116 // System.out.println("请输入下载文件的目录: "); 117 // String targetFile = scan.next(); 118 // System.out.println("请输入线程数: "); 119 // int threadNum = scan.nextInt(); 120 // final DownUtil downUtil = new DownUtil(path, targetFile, threadNum); 121 final DownUtil downUtil = new DownUtil("http://dlsw.baidu.com/sw-search-sp/soft/3a/12350/QQ6.1.1406080907.exe", 122 "C:\\Users\\Administrator\\Desktop\\a\\qq.exe", 10); 123 downUtil.download(); 124 new Thread() { 125 public void run() { 126 while (downUtil.getCompleteRate() < 1) { 127 System.out.printf("\r已完成: %.2f%%", downUtil.getCompleteRate()*100); 128 try { 129 Thread.sleep(1000); 130 } catch (Exception e) { 131 e.printStackTrace(); 132 } 133 } 134 } 135 }.start(); 136 /** 137 * 输入: 138 http://dlsw.baidu.com/sw-search-sp/soft/3a/12350/QQ6.1.1406080907.exe 139 C:\Users\Administrator\Desktop\a\qq.exe 140 10 141 */ 142 } 143 }
在上面的程序中,先使用RandomAccessFile新建一个文件并设置其大小,关闭连接,再重新连接,之后分配每个线程下载的范围,然后在for循环中打开每一个线程即可下载。在多线程下载工具中,通常会用一个额外的文件来保存每个线程下载的范围以及当前下载到哪个字节,这样就能做到断点续传
执行多线程任务,可以new出一个线程数组,然后循环打开线程好了,注意任务分配。
5. 如果既要使用输入流读取URLConnection响应的内容,又要使用输出流发送请求参数,则一定要先使用输出流,再使用输入流。
6. get和post请求方式
1 package c17_2; 2 3 import java.io.BufferedReader; 4 import java.io.InputStreamReader; 5 import java.io.PrintWriter; 6 import java.net.URL; 7 import java.net.URLConnection; 8 import java.util.List; 9 import java.util.Map; 10 11 public class GetPostTest { 12 public static String sendGet(String url, String param) { 13 String result = ""; 14 String urlName= url + "?" + param; 15 try { 16 URL realUrl = new URL(urlName); 17 URLConnection conn = realUrl.openConnection(); 18 conn.setRequestProperty("Accept", "*/*"); 19 conn.setRequestProperty("connection", "Keep-Alive"); 20 conn.setRequestProperty("user-agent", "Mozilla/5.0 " 21 + "(Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " 22 + "Chrome/28.0.1500.72 Safari/537.36"); 23 conn.connect(); 24 Map<String, List<String>> map = conn.getHeaderFields(); 25 for (String key : map.keySet()) {//打印请求头的键 26 System.out.println(key + "-------->" + map.get(key)); 27 } 28 BufferedReader in = null; 29 try { 30 in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); 31 String line = null; 32 while ((line = in.readLine()) != null) {//打印相印内容 33 result += "\n" + line; 34 } 35 } catch(Exception e) { 36 e.printStackTrace(); 37 } finally { 38 if (in != null) { 39 in.close(); 40 } 41 } 42 } catch(Exception e) { 43 e.printStackTrace(); 44 } 45 return result; 46 } 47 public static String sendPost(String url, String param) { 48 String result = ""; 49 try { 50 URL realUrl = new URL(url); 51 URLConnection conn = realUrl.openConnection(); 52 conn.setRequestProperty("Accept", "*/*"); 53 conn.setRequestProperty("connection", "Keep-Alive"); 54 conn.setRequestProperty("user-agent", "Mozilla/5.0 " 55 + "(Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " 56 + "Chrome/28.0.1500.72 Safari/537.36"); 57 //***************使用post必须设置这两个************************** 58 conn.setDoInput(true); 59 conn.setDoOutput(true); 60 PrintWriter out = null; 61 try {//传参数的块 62 out = new PrintWriter(conn.getOutputStream()); 63 out.print(param);//把参数传进去 64 } catch(Exception e) { 65 e.printStackTrace(); 66 } finally { 67 if (out != null) { 68 out.close(); 69 } 70 } 71 72 BufferedReader in = null; 73 try {//获取响应的块 74 in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); 75 String line = null; 76 while ((line = in.readLine()) != null) {//打印相印内容 77 result += "\n" + line; 78 } 79 } catch(Exception e) { 80 e.printStackTrace(); 81 } finally { 82 if (in != null) { 83 in.close(); 84 } 85 } 86 } catch(Exception e) { 87 e.printStackTrace(); 88 } 89 90 return result; 91 } 92 public static void main(String[] args) throws Exception { 93 String s = GetPostTest.sendGet("http://anime.kankan.com/", null); 94 String s1 = GetPostTest.sendPost("http://www.baidu.com/index.php", "tn=10018802_hao"); 95 System.out.println(s1); 96 } 97 }
get方式用拼接url来请求,用connect()连接就行。post方式需要设置conn.setDoInput(true);和conn.setDoOutput(true);然后flush或close一下输出流就能发出请求。设置doInput为false就不能获得响应,设置doOutput为false就不能发送请求,关闭其中一个可以节省资源
7. socket是基于TCP/IP协议的
8. 使用ServerSocket创建TCP服务器端,
如果接收到一个客户端Socket的连接请求,其accept方法将返回一个与客户端的Socket对应的Socket;否则该方法将一直处于等待状态,线程也被阻塞
public class Server { public static void main(String[] args) throws Exception { ServerSocket server = new ServerSocket(30000); while (true) { Socket s = server.accept(); PrintStream ps = new PrintStream(s.getOutputStream()); ps.println("您好,您收到了服务器的祝福"); ps.close(); s.close(); } } }
9. Socket可以获得输入流和输出流
public class Client { public static void main(String[] args) throws Exception { Socket socket = new Socket("127.0.0.1", 30000); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = br.readLine(); System.out.println("来自服务器的数据:" + line); br.close(); socket.close(); } }
一次运行上面两个程序,客户端运行的时候就会收到服务器端的信息
另外socket还可以设置超时时长:setSoTimeOut(int timeout);需要用try-catch。还可以设置连接超时时长:connection(SocketAddress ip, int timeout);
10. 每个Socket读输入流都会造成线程阻塞,所以应该单独启动一个线程来读输入流
11. 控制台多人聊天室
协议类:用来标识信息是哪种类型
1 public interface Protocol { 2 String MSG_ROUND = "MSG:";//普通信息 3 String USER_ROUND= "USER:";//用户名 4 String LOGIN_SUCCESS = "LOGIN:";//成功登录 5 String NAME_REP = "NAME_REP:";//用户名重复 6 String PRIVATE_ROUND = "PRIVATE:";//悄悄话 7 String SPLIT_SIGN = "SPLIT:";//分隔符 8 }
用户名和输入流的映射类:
1 public class MsgMap<K, V> extends HashMap<K, V> {//这个map用来保存用户名和Socket的输出流的对应关系 2 public void removeByValue(Object value) {//通过值来删除键值对 3 for (Object key : keySet()) { 4 if (get(key) == value) { 5 remove(key); 6 break; 7 } 8 } 9 } 10 public Set<V> valueSet() {//返回 “值set” 11 Set<V> result = new HashSet<V>(); 12 for (K key : keySet()) { 13 result.add(get(key)); 14 } 15 return result; 16 } 17 public K getKeyByValue(V val) {//通过value获得key 18 for (K key : keySet()) { 19 if (get(key). equals(val) && get(key) == val) { 20 return key; 21 } 22 } 23 return null; 24 } 25 public V put(K key, V value) { 26 for (V val : valueSet()) { 27 if (val.equals(value) && (val.hashCode() == value.hashCode())) { 28 throw new RuntimeException("MsgMap实例中不允许有重复value!"); 29 } 30 } 31 return super.put(key, value); 32 } 33 34 }
1 public class MyServer { 2 private static final int SERVER_PORT = 30000; 3 public static MsgMap<String, PrintStream> clients = new MsgMap<>(); 4 public void init() { 5 try ( 6 ServerSocket server = new ServerSocket(SERVER_PORT); 7 ) { 8 System.out.println("已启动聊天室服务器,请勿关闭此程序"); 9 while (true) { 10 Socket socket = server.accept(); 11 new ServerThread(socket).start(); 12 } 13 } catch(Exception e) { 14 System.out.println("服务器启动失败,请检查端口" + SERVER_PORT + "是否已被占用"); 15 } 16 } 17 public static void main(String[] args) throws Exception { 18 MyServer server = new MyServer(); 19 server.init(); 20 } 21 private class ServerThread extends Thread { 22 Socket socket = null; 23 BufferedReader br = null; 24 PrintStream ps = null; 25 public ServerThread(Socket socket) throws IOException { 26 this.socket = socket; 27 //千万别忘记编码,因为控制台下的编码默认是GBK 28 br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); 29 ps = new PrintStream(socket.getOutputStream(), true, "UTF-8"); 30 } 31 public void run() { 32 try { 33 String line = null; 34 while ((line = br.readLine()) != null) { 35 if (line.startsWith(Protocol.USER_ROUND)) { 36 String userName = getRealMsg(line); 37 if (MyServer.clients.containsKey(userName)) { 38 //System.out.println("重复"); 39 ps.println(Protocol.NAME_REP); 40 } else { 41 ps.println(Protocol.LOGIN_SUCCESS); 42 MyServer.clients.put(userName, ps); 43 for (PrintStream userStream : MyServer.clients.values()) { 44 userStream.println(userName + "进入聊天室"); 45 } 46 } 47 } else if (line.startsWith(Protocol.PRIVATE_ROUND)) { 48 String userAndMsg = getRealMsg(line); 49 String user = userAndMsg.split(Protocol.SPLIT_SIGN)[0]; 50 String msg = userAndMsg.split(Protocol.SPLIT_SIGN)[1]; 51 MyServer.clients.get(user).println(MyServer.clients.getKeyByValue(ps) + "悄悄地对你说: " + msg); 52 } else { 53 String msg = getRealMsg(line); 54 for (PrintStream clientPs : MyServer.clients.valueSet()) { 55 clientPs.println(MyServer.clients.getKeyByValue(ps) + "说: " + msg); 56 } 57 } 58 } 59 } catch(IOException e) { 60 String userName = MyServer.clients.getKeyByValue(ps); 61 MyServer.clients.removeByValue(ps); 62 for (PrintStream userStream : MyServer.clients.values()) { 63 userStream.println(userName + "离开聊天室"); 64 } 65 //System.out.println(MyServer.clients.size()); 66 if (br != null) { 67 try { 68 br.close(); 69 } catch (IOException ioe) { 70 //ioe.printStackTrace(); 71 72 } 73 } 74 if (ps != null) { 75 ps.close(); 76 } 77 if (socket != null) { 78 try { 79 socket.close(); 80 } catch (IOException ioe) { 81 //ioe.printStackTrace(); 82 } 83 } 84 85 } 86 87 } 88 private String getRealMsg(String line) { 89 return line.substring(line.indexOf(':')+1); 90 } 91 } 92 93 }
1 public class MyClient { 2 private static final int SERVER_PORT = 30000; 3 private Socket socket; 4 private PrintStream ps; 5 private BufferedReader brServer; 6 private BufferedReader keyIn; 7 public void init() { 8 try { 9 keyIn = new BufferedReader(new InputStreamReader(System.in)); 10 Socket s = new Socket("127.0.0.1", SERVER_PORT); 11 ps = new PrintStream(s.getOutputStream(), true, "UTF-8"); 12 brServer = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8")); 13 String tip = ""; 14 while (true) { 15 String userName = JOptionPane.showInputDialog(tip + "输入用户名"); 16 ps.println(Protocol.USER_ROUND + userName); 17 String result = brServer.readLine(); 18 if (result.equals(Protocol.NAME_REP)) { 19 tip = "用户名重复!请重新"; 20 continue; 21 } 22 if (result.equals(Protocol.LOGIN_SUCCESS)) { 23 break; 24 } 25 } 26 } catch(UnknownHostException e) { 27 System.out.println("找不到远程服务器,请确定服务器已经启动!"); 28 closeRs(); 29 System.exit(1); 30 } catch(IOException e) { 31 System.out.println("连接远程服务器失败!"); 32 closeRs(); 33 System.exit(1); 34 } 35 36 this.new ClientThread(brServer).start(); 37 } 38 private void readAndAndSend() { 39 try { 40 41 String line = null; 42 while ((line = keyIn.readLine()) != null) { 43 //如果发送的信息中有冒号,且以/开头,则认为想发送私聊信息 44 if (line.indexOf(":") > 0 && line.startsWith("/")) { 45 line = line.substring(1); 46 ps.println(Protocol.PRIVATE_ROUND + line.split(":")[0] + Protocol.SPLIT_SIGN + line.split(":")[1]); 47 } else { 48 ps.println(Protocol.MSG_ROUND + line); 49 } 50 } 51 } catch (IOException e) { 52 System.out.println("网络通信异常!请重新登录"); 53 closeRs(); 54 System.exit(1); 55 } 56 } 57 private void closeRs() { 58 try { 59 if (keyIn != null) { 60 keyIn.close(); 61 } 62 if (brServer != null) { 63 brServer.close(); 64 } 65 if (ps != null) { 66 ps.close(); 67 } 68 if (socket != null) { 69 socket.close(); 70 } 71 } catch (IOException e) { 72 //e.printStackTrace(); 73 } 74 } 75 public static void main(String[] args) throws Exception { 76 MyClient client = new MyClient(); 77 client.init(); 78 client.readAndAndSend(); 79 } 80 private class ClientThread extends Thread { 81 BufferedReader br = null; 82 public ClientThread(BufferedReader br) { 83 this.br = br; 84 } 85 public void run() { 86 try { 87 String line = null; 88 while ((line = br.readLine()) != null) { 89 System.out.println(line); 90 } 91 } catch(Exception e) { 92 // e.printStackTrace(); 93 System.out.println("连接聊天室失败!"); 94 } finally { 95 if (br != null) { 96 try { 97 br.close(); 98 } catch (IOException e) { 99 e.printStackTrace(); 100 } 101 } 102 } 103 } 104 } 105 106 }
效果:
13. 半关闭的socket可以节省资源
shutdownOutput()/shutdownInput()
如果两个方法都调用了,socket还是可用的,只不过无法输入输出,可以通过socket.isClosed()检查socket是否关闭。
例如HTTP协议,发送请求后只需接收数据,因此发出请求后可以关闭输出流
14. 使用NIO的非阻塞Socket通信
阻塞Socket和非阻塞Socket的区别在于:
非阻塞Socket的accept()方法不会阻塞,如果没有客户连接,则返回一个负数然后继续执行,read()也是差不多。
使用非阻塞Socket可以仅用一个线程就能与多个用户交互,而使用阻塞Socket时每个用户都需要一个线程。
具体的理解可参见这篇文章:http://www.cnblogs.com/QQParadise/articles/1351493.html
Selector是SelectableChannel对象的多路复用器,所有希望采用非阻塞方式进行通信的Channel都应该注册到Selector对象。Selector可以同时监控多个SelectableChannel的IO状况,是非阻塞IO的核心。一个Selector实例有3个SelectionKey集合。通过keys方法可以返回所有的SelectionKey集合,通过selectedKeys方法可以返回被选择的SelectionKey集合,代表了所有可通过select方法获取的需要进行IO处理的Channel。被取消的SelectionKey集合无法获取。
select()监控所有被注册的Channel,当它们需要处理IO操作时,该方法才返回(阻塞),并将对应的SelectionKey加入被注册的SelectionKey集合中,返回这些Channel的数量
select(long timeout)设定超时时长的select()操作
selectNow()执行一个立即返回的select()操作,该方法不会阻塞线程
wakeup()使一个还未返回的select()方法立即返回
SelectableChannel支持阻塞和非阻塞两种模式,所有的Channel默认都是阻塞模式。
configureBlocking(boolean block)可以设置采用阻塞模式并返回channel
isBlocking()可以查看是否阻塞模式
isRegistered(),是否已注册
keyFor(Selector sel), 如果和sel存在注册关系,则返回SelecionKey,反则返回null
validOps()返回一个正数代表支持的IO操作,1:OP_READ, 4: OP_WRITE, 8:OP_ACCEPT, 16: OP_ACCEPT, 任意2个、3个、4个相加的结果总是不想等
ServerSocketChannel代表一个ServerSocket,它仅支持OP_ACCEPT操作
SocketChannel支持OP_CONNECT, OP_READ和 OP_WRITE操作,这个类还是先ByteChannel接口、ScatteringByteChannel接口和GatheringByteChannel接口,所以可以直接通过SocketChannel来读写ByteChannel对象。这些操作都是SelectionKey的静态变量
15. NIO多人聊天室
这个程序功能了一点,仅示范怎么使用NIOSocket。但是稍微难理解,关键是要理解非阻塞的机制,服务端的while (selector.select() > 0)会阻塞线程,之所以会阻塞线程,是因为Selector需要轮询有那些channel需要读写,当有channel需要读写的时候,才有返回值。当有需要读写channel时,首先删除它的SelectionKey,然后再指定该SelectionKey监听的下一次时间,程序就在这样不断的删除、添加SelectorKey中运行着,如果不删除活动的SelectionKey的话,到下次该key对应的channel需要读写时,之前没删除的key也会被视为没处理并执行,会导致错误而使程序崩溃。调用select()时会再次返回值。当客户端关闭的时候,select()也会有返回值,而且是readable的,进入读的if块后走到while(sc.read(buff) > 0)会抛出异常,在catch中cancel该channel。
1 public class NIOServer { 2 private Selector selector = null; 3 static final int PORT = 30000; 4 private Charset charset = Charset.forName("UTF-8"); 5 public void init() throws IOException { 6 selector = Selector.open();//通过静态方法获得Selector 7 ServerSocketChannel server = ServerSocketChannel.open();//都是通过open()获得实例 8 InetSocketAddress address = new InetSocketAddress("127.0.0.1", PORT); 9 server.bind(address);//服务器别忘了绑定ip 10 server.configureBlocking(false);//非阻塞 11 server.register(selector, SelectionKey.OP_ACCEPT);//将服务器注册到selector,指定其操作 12 13 while (selector.select() > 0) {//会造成线程阻塞 14 System.out.println(selector.selectedKeys().size()); 15 for (SelectionKey serverKey : selector.selectedKeys()) { 16 // selector.selectedKeys().remove(serverKey);//删除server的操作 17 System.out.println("SelectionKey是否合法: " + serverKey.isValid()); 18 //如果sk对应的channel包含客户端的连接请求 19 if (serverKey.isAcceptable()) { 20 selector.selectedKeys().remove(serverKey);//删除server的操作 21 SocketChannel sc = server.accept();//连接用accept 22 sc.configureBlocking(false); 23 sc.register(selector, SelectionKey.OP_READ);//为了读客户端的请求,注册 24 serverKey.interestOps(SelectionKey.OP_ACCEPT);//为server添加accept操作的key 25 } 26 //如果sk对应的channel有数据需要读取 27 if (serverKey.isReadable()) { 28 SocketChannel sc = (SocketChannel)serverKey.channel();//读信息用channel 29 ByteBuffer buff = ByteBuffer.allocate(1024); 30 String content = ""; 31 try { 32 while (sc.read(buff) > 0) { 33 buff.flip(); 34 content += charset.decode(buff); 35 } 36 System.out.println("读取的数据: " + content); 37 serverKey.interestOps(SelectionKey.OP_READ);//为server添加读操作的key 38 } catch(IOException e) { 39 //当客户端关闭时,会导致异常,此时应该删除改SelectionKey,否则服务端也会退出程序 40 serverKey.cancel();//删除在Selector中sk这个SelectionKey 41 if (serverKey.channel() != null) { 42 serverKey.channel().close(); 43 } 44 } 45 //如果content的长度大于0,即聊天信息不为空 46 if (content.length() > 0) { 47 for (SelectionKey key : selector.keys()) {//把信息发送到所有的客户端 48 Channel targetChannel = key.channel(); 49 if (targetChannel instanceof SocketChannel) { 50 SocketChannel dest = (SocketChannel)targetChannel; 51 dest.write(charset.encode(content)); 52 } 53 } 54 } 55 } 56 } 57 } 58 } 59 public static void main(String[] args) throws IOException { 60 new NIOServer().init(); 61 } 62 }
1 public class NIOClient { 2 private Selector selector; 3 static final int PORT = 30000; 4 private Charset charset = Charset.forName("UTF-8"); 5 private SocketChannel sc; 6 public void init() throws IOException { 7 selector = Selector.open(); 8 InetSocketAddress address = new InetSocketAddress("127.0.0.1", PORT); 9 sc = SocketChannel.open(address); 10 sc.configureBlocking(false); 11 sc.register(selector, SelectionKey.OP_READ); 12 this.new ClientThread().start();//单独开一个线程读取别人发的信息 13 Scanner scan = new Scanner(System.in); 14 while (scan.hasNextLine()) {//主线程读键盘输入 15 String line = scan.nextLine(); 16 sc.write(charset.encode(line)); 17 } 18 } 19 private class ClientThread extends Thread { 20 public void run() { 21 try { 22 while (selector.select() > 0) { 23 for (SelectionKey sk : selector.selectedKeys()) { 24 selector.selectedKeys().remove(sk); 25 if (sk.isReadable()) { 26 SocketChannel sc = (SocketChannel)sk.channel(); 27 ByteBuffer buff = ByteBuffer.allocate(1024); 28 String content = ""; 29 while (sc.read(buff) > 0) { 30 sc.read(buff); 31 buff.flip(); 32 content += charset.decode(buff); 33 } 34 System.out.println("聊天信息: " + content); 35 sk.interestOps(SelectionKey.OP_READ); 36 } 37 } 38 } 39 } catch(IOException e) { 40 e.printStackTrace(); 41 } 42 } 43 } 44 public static void main(String[] args) throws IOException { 45 new NIOClient().init(); 46 } 47 48 }
16. 使用java7的AIO实现非阻塞通信
java7的NIO.2提供了异步Channel支持,这种异步Channel可以提供更高校的IO,这种基于异步Channel的IO机制也被称为异步IO(Asynchronous IO)
同步IO与异步IO的区别在于:如果时机的IO操作由操作系统完成,再将结果返回给应用程序,就是异步IO;如果时机的IO需要应用程序本身去执行,会阻塞线程,那就是同步IO。前面写到IO都是同步IO
如果学过js的ajax,就知道什么是“异步”了:另开一个线程去监听响应,如果响应到来,就会触发回调函数,但不知道何时响应才会到来。
AsynchronousServerSocketChannel 创建方式和ServerSocketChannel类似,都是通过open获得实例,bind绑定ip。open(AsynchronousChannelGroup group): 使用指定的AsynchronousChannelGroup来创建AsychronousServerSocketChannel。AsychronousServerSocketChannel是异步Channel的分组管理器,它可以实现资源共享。创建AsynchronousChannelGroup时需要传入一个ExecutorService,也就是说,它会绑定一个线程池,该线程池负责两个任务:处理IO事件、出发CompletionHandler
ExecutorService executor = Executors.newFixedThreadPool(80); AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(executor); AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(channelGroup).bind(new InetSocketAddress(30000));
值得注意的是AsynchronousServerSocketChannel并不是ServerSocketChannel的子类
AsychronousSocketChannel也可以在调用open方法时传入一个线程池
异步IO的accept()可以接收来自客户端的连接,不会阻塞,但不知道客户端请求什么时候到,因此提供两个版本:
Future<AsynchronousSocketChannel> accept():不会阻塞,但是调用Future的get()获得返回值时会阻塞
<A> void accept<A attachment, CompletionHandler<AsynchronousSocketChannel, ? super A>handler): 无论客户端连接成功或失败,都会出发handler的方法
CompletionHandler是个接口,其中有两个方法:completed(V result, A attachment):result-返回的对象, attachment 发起IO操作时传入的附加参数; failed(Throwable exec, A attachment)。
可以CompletionHandler的时,可以认为,completed和failed方法就是回调函数。
AsynchronousSocketChannel的read()和write()也可以传入CompletionHandler
17. 简单的AIO服务端客户端
1 public class SimpleAIOServer { 2 3 static final int PORT = 30000; 4 public static void main(String[] args) throws Exception { 5 try( 6 AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(); 7 ) { 8 serverChannel.bind(new InetSocketAddress(PORT)); 9 while (true) { 10 Future<AsynchronousSocketChannel> future = serverChannel.accept(); 11 AsynchronousSocketChannel socketChannel = future.get();//阻塞 12 socketChannel.write(ByteBuffer.wrap("hello AIO".getBytes("UTF-8"))).get();//阻塞 13 } 14 } 15 } 16 17 }
1 public class SimpleAIOClient { 2 static final int PORT = 30000; 3 public static void main(String[] args) throws Exception { 4 ByteBuffer buff = ByteBuffer.allocate(1024); 5 Charset utf = Charset.forName("UTF-8"); 6 try ( 7 AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open(); 8 ) { 9 clientChannel.connect(new InetSocketAddress("127.0.0.1", PORT)).get();//阻塞 10 buff.clear(); 11 clientChannel.read(buff).get();//阻塞 12 buff.flip(); 13 String content = utf.decode(buff).toString(); 14 System.out.println("服务器信息: " + content); 15 } 16 17 } 18 } 19 }
18. AIO聊天程序,带Swing窗口
1 public class AIOServer { 2 static final int PORT = 30000; 3 final static String UTF_8 = "utf-8"; 4 static List<AsynchronousSocketChannel> channelList = new ArrayList<>(); 5 public void startListen() throws Exception { 6 ExecutorService executor = Executors.newFixedThreadPool(20); 7 AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(executor); 8 AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(channelGroup) 9 .bind(new InetSocketAddress(PORT)); 10 serverChannel.accept(null, new AcceptHandler(serverChannel));//AcceptHandler是自定义的 11 while (true) {}//别少了这句,不然程序早结束了 12 } 13 class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> { 14 private AsynchronousServerSocketChannel serverChannel; 15 private ByteBuffer buff = ByteBuffer.allocate(1024); 16 public AcceptHandler(AsynchronousServerSocketChannel sc) { 17 this.serverChannel = sc; 18 } 19 20 @Override 21 public void completed(final AsynchronousSocketChannel sc, 22 Object attachment) { 23 AIOServer.channelList.add(sc); 24 serverChannel.accept(null, this); 25 sc.read(buff, null, new CompletionHandler<Integer, Object>() { 26 @Override 27 public void completed(Integer result, Object attachment) { 28 buff.flip(); 29 String content = StandardCharsets.UTF_8.decode(buff).toString(); 30 for (AsynchronousSocketChannel c : AIOServer.channelList) { 31 try { 32 c.write(ByteBuffer.wrap(content.getBytes(AIOServer.UTF_8))).get(); 33 } catch (Exception e) { 34 e.printStackTrace(); 35 } 36 } 37 buff.clear(); 38 sc.read(buff, null, this); 39 } 40 41 @Override 42 public void failed(Throwable exc, Object attachment) { 43 System.out.println("读取数据失败: " + exc); 44 AIOServer.channelList.remove(sc); 45 } 46 47 }); 48 } 49 50 @Override 51 public void failed(Throwable exc, Object attachment) { 52 System.out.println("连接失败: " + exc); 53 } 54 55 } 56 public static void main(String[] args) throws Exception { 57 AIOServer server = new AIOServer(); 58 server.startListen(); 59 } 60 61 }
1 public class AIOClient { 2 static final int PORT = 30000; 3 final static String UTF_8 = "utf-8"; 4 AsynchronousSocketChannel clientChannel; 5 JFrame mainWin = new JFrame("多人聊天"); 6 JTextArea jta = new JTextArea(16, 48); 7 JTextField jtf = new JTextField(40); 8 JButton sendBn = new JButton("发送"); 9 10 public void init() { 11 mainWin.setLayout(new BorderLayout()); 12 jta.setEditable(false); 13 mainWin.add(new JScrollPane(jta), BorderLayout.CENTER); 14 JPanel jp = new JPanel(); 15 jp.add(jtf); 16 jp.add(sendBn); 17 Action sendAction = new AbstractAction() { 18 @Override 19 public void actionPerformed(ActionEvent e) { 20 String content = jtf.getText(); 21 if (content.trim().length() > 0) { 22 try { 23 clientChannel.write(ByteBuffer.wrap(content.trim().getBytes(UTF_8))).get(); 24 } catch (Exception ex) { 25 ex.printStackTrace(); 26 } 27 } 28 jtf.setText(""); 29 } 30 }; 31 sendBn.addActionListener(sendAction); 32 jtf.getInputMap().put(KeyStroke.getKeyStroke('\n', java.awt.event.InputEvent.CTRL_MASK) 33 , "send"); 34 jtf.getActionMap().put("send", sendAction); 35 mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 36 mainWin.add(jp, BorderLayout.SOUTH); 37 mainWin.pack(); 38 mainWin.setVisible(true); 39 } 40 public void connect() throws Exception { 41 final ByteBuffer buff = ByteBuffer.allocate(1024); 42 ExecutorService executor = Executors.newFixedThreadPool(80); 43 AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(executor); 44 clientChannel = AsynchronousSocketChannel.open(channelGroup); 45 clientChannel.connect(new InetSocketAddress("127.0.0.1", PORT)).get(); 46 jta.append("-----与服务器连接成功-----\n"); 47 buff.clear(); 48 clientChannel.read(buff, null, new CompletionHandler<Integer, Object>() { 49 50 @Override 51 public void completed(Integer result, Object attachment) { 52 buff.flip(); 53 String content = StandardCharsets.UTF_8.decode(buff).toString(); 54 jta.append("某人说: " + content + "\n"); 55 buff.clear(); 56 clientChannel.read(buff, null, this); 57 } 58 59 @Override 60 public void failed(Throwable exc, Object attachment) { 61 System.out.println("读取数据失败: " + exc); 62 } 63 64 }); 65 } 66 public static void main(String[] args) throws Exception { 67 AIOClient client = new AIOClient(); 68 client.init(); 69 client.connect(); 70 71 } 72 73 }
效果:
与js的ajax不同的是:java的异步IO绑定一次事件之后该事件执行一次后事件就失效了,必须不停的绑定下一次的事件
19. 代理服务器
java.net.Proxy代表一个代理服务器,可以在打开URLConnection连接时指定Proxy,创建Socket连接时也可以指定Proxy。
而ProxySelector代表一个代理选择器,它提供了对代理服务器更加灵活的控制。
创建Proxy时可以指定连接方式:
Proxy.Type.DIRECT:直接连接,不使用代理。 Proxy.Type.HTTp: 支持高级协议代理,如HTTP和FTP. Proxy.Type.SOCKS: 代表SOCKS代理
通过代理服务器下载网页来查看:
1 public class ProxyTest { 2 //代理服务器和端口在这里找: http://www.xici.net.co/ 3 final String PROXY_ADDR = "198.136.50.131"; 4 final int PROXY_PORT = 7808; 5 String urlStr = "http://www.hao123.com/"; 6 7 public void init() throws Exception { 8 URL url = new URL(urlStr); 9 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_ADDR, PROXY_PORT)); 10 URLConnection conn = url.openConnection(proxy); 11 conn.setConnectTimeout(5000); 12 try ( 13 Scanner scan = new Scanner(conn.getInputStream()); 14 PrintStream ps = new PrintStream("c:/index.htm");//把scan读到的内容放到index.htm中方便查看 15 ) { 16 while (scan.hasNextLine()) { 17 String line = scan.nextLine(); 18 System.out.println(line); 19 ps.println(line); 20 } 21 } 22 System.out.println("ok"); 23 } 24 25 public static void main(String[] args) throws Exception { 26 new ProxyTest().init(); 27 } 28 29 }
ProxySelector的使用:
1 public class ProxySelectorTest { 2 final String PROXY_ADDR = "139.82.12.188"; 3 final int PROXY_PORT = 3124; 4 String urlStr = "http://www.hao123.com"; 5 6 public void init() throws Exception { 7 ProxySelector.setDefault(new ProxySelector() {//因为ProxySelector是抽象类,所以要实现子类 8 @Override 9 public List<Proxy> select(URI uri) {//根据uri返回代理服务器链 10 List<Proxy> result = new ArrayList<>(); 11 result.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_ADDR, PROXY_PORT))); 12 return result; 13 } 14 15 @Override 16 public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { 17 System.out.println("无法连接到指定代理服务器!"); 18 } 19 }); 20 URL url = new URL(urlStr); 21 /* 这里虽然没有选择代理服务器,但实际上会调用ProxySelector的default代理服务器 22 * 而最初的default代理服务器会直接打开url连接 23 */ 24 URLConnection conn = url.openConnection(); 25 conn.connect();//将调用ProxySelector的connectFailed方法,因为代理服务器ip不正确, 26 } 27 28 public static void main(String[] args) throws Exception { 29 new ProxySelectorTest().init(); 30 } 31 32 }
ProxySelector最初的默认ProxySelector:sun.net.spi.DefaultProxySelector,这是一个未公开类,尽量别使用该类
它根据系统属性来创建Proxy:
1 public class DefaultProxySelectorTest { 2 static String urlStr = "http://www.hao123.com"; 3 4 public static void main(String[] args) throws Exception { 5 Properties props = System.getProperties(); 6 props.setProperty("http.proxyHost", "192.168.10.96"); 7 props.setProperty("http.proxyPort", "8080"); 8 props.setProperty("http.nonProxyHosts", "localhost|192.168.10.*"); 9 props.setProperty("https.proxyHost", "192.168.10.96"); 10 props.setProperty("https.proxyPort", "443"); 11 props.setProperty("ftp.proxyHost", "192.168.10.96"); 12 props.setProperty("ftp.proxyPort", "2121"); 13 props.setProperty("ftp.nonProxyHosts", "localhost|192.168.10.*"); 14 props.setProperty("socks.proxyHost", "192.168.10.96"); 15 props.setProperty("socks.proxyPort", "1080"); 16 17 ProxySelector selector = ProxySelector.getDefault(); 18 System.out.println(selector); 19 System.out.println(selector.select(new URI("ftp://www.crazyit.org"))); 20 URL url = new URL(urlStr); 21 URLConnection conn = url.openConnection(); 22 conn.setConnectTimeout(3000); 23 try ( 24 Scanner scan = new Scanner(conn.getInputStream(), "utf-8"); 25 ) { 26 while (scan.hasNextLine()) { 27 System.out.println(scan.nextLine()); 28 } 29 } 30 } 31 32 }
192.168.10.96通常不是一个代理服务器的,之所以能传网页信息,是因为DefaultProxySelector的connectFail()方法直接连接远程资源
20. DatagramSocket和DatagramPacket
TCP协议安全可靠,但是需要时间和更多数据来保证可靠性。UDP协议是面向非连接的协议,每次至传送少量数据,仅传出信息而不保证传过去的信息是完整、正确的,也不保证对方能否接收到信息,但是速度很快,因此适用于实时性高的场景,比如网络游戏、视频会议等。
DatagramSocket就是UDP的socket,不能产生IO流,唯一的作用是接收和发送数据报(DatagramPacket)
使用UDP协议编程时,没有明显的服务器,因为双方都要建立一个DatagramSocket对象。通常固定IP做服务器
这两个程序不能在同一个ip下使用不然会有错误:端口被占用。可以用虚拟机来做测试,注意该ip
1 public class UDPServer { 2 public static final int PORT = 30000; 3 private static final int DATA_LEN = 4096; 4 byte[] inBuff = new byte[DATA_LEN]; 5 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); 6 private DatagramPacket outPacket; 7 8 String[] books = new String[] { 9 "收获,不止oracle", 10 "大话设计模式", 11 "数据结构", 12 "How tomcat works" 13 }; 14 15 public void init() throws Exception { 16 try ( 17 DatagramSocket socket = new DatagramSocket(PORT); 18 ) { 19 for (int i = 0; i < 1000; ++i) { 20 socket.receive(inPacket);//接收数据,阻塞 21 System.out.println(inBuff == inPacket.getData());//实际上是相等的 22 System.out.println(new String(inBuff, 0, inPacket.getLength())); 23 byte[] sendData = books[i%4].getBytes(); 24 outPacket = new DatagramPacket(sendData, sendData.length, inPacket.getSocketAddress()); 25 socket.send(outPacket);//发送数据,阻塞 26 } 27 } 28 } 29 30 public static void main(String[] args) throws Exception { 31 new UDPServer().init(); 32 } 33 34 }
1 public class UDPClient { 2 public static final int DEST_PORT = 30000; 3 public static final String DEST_IP = "127.0.0.1"; 4 private static final int DATA_LEN = 4096; 5 byte[] inBuff = new byte[DATA_LEN]; 6 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); 7 private DatagramPacket outPacket; 8 9 public void init() throws Exception { 10 try ( 11 DatagramSocket socket = new DatagramSocket(DEST_PORT); 12 ) { 13 outPacket = new DatagramPacket(new byte[0], 0, InetAddress.getByName(DEST_IP), DEST_PORT); 14 Scanner scan = new Scanner(System.in); 15 while (scan.hasNextLine()) { 16 byte[] buff = scan.nextLine().getBytes(); 17 outPacket.setData(buff); 18 socket.send(outPacket); 19 socket.receive(inPacket); 20 System.out.println(new String(inBuff, 0, inPacket.getLength())); 21 } 22 } 23 } 24 public static void main(String[] args) throws Exception { 25 new UDPClient().init(); 26 } 27 28 }
我在windows的eclipse开了服务端,用ubutnu虚拟机的eclipse打开客户端:
DatagramSocket只允许数据报发送给指定的目录地址,而MulticastSocket可以将数据报以广播的方法发送到多个客户端。IP协议为多点广播提供了广播IP地址,范围是224.0.0.0~239.255.255.255。实际上MulticastSocket是DatagramSocket的一个子类。MulticastSocket比DatagramSocket多了一个setTimeToLive(int ttl)方法,该ttl参数用于设置数报最多可以跨过多少个网络:值为0时,数据包只停留在本地主机;1时,本地局域网;32,本站点的网络上;64,本地区;128,本大洲;255,所有地方。在默认情况下ttl值为1
简单的广播服务器:
1 public class MulticastSocketTest implements Runnable { 2 private static final String BROADCAST_IP = "224.0.1";//如果不在224.0.0.0~239.255.255.255就报错 3 public static final int BROADCAST_PORT = 30000; 4 private static final int DATA_LEN = 4096; 5 private MulticastSocket socket; 6 private InetAddress broadcastAddress; 7 private Scanner scan; 8 private byte[] inBuff = new byte[DATA_LEN]; 9 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); 10 private DatagramPacket outPacket; 11 12 public void init() throws Exception { 13 try ( 14 Scanner scan = new Scanner(System.in); 15 ) { 16 socket = new MulticastSocket(BROADCAST_PORT); 17 broadcastAddress = InetAddress.getByName(BROADCAST_IP);//可以通过域名获得ip 18 socket.joinGroup(broadcastAddress);//加入多点广播 19 socket.setLoopbackMode(false);//发出的数据是否送回,false送回,true不送回 20 outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress, BROADCAST_PORT); 21 new Thread(this).start(); 22 while (scan.hasNextLine()) { 23 byte[] buff = scan.nextLine().getBytes();//读键盘输入 24 outPacket.setData(buff); 25 socket.send(outPacket);//发送数据包 26 } 27 } finally { 28 socket.close(); 29 } 30 } 31 32 @Override 33 public void run() { 34 try { 35 while (true) { 36 socket.receive(inPacket);//接收数据报 37 System.out.println("聊天信息" + new String(inBuff, 0, inPacket.getLength())); 38 } 39 } catch(Exception e) { 40 e.printStackTrace(); 41 try { 42 if (socket != null) { 43 socket.leaveGroup(broadcastAddress); 44 socket.close(); 45 } 46 System.exit(1); 47 } catch (Exception ex) { 48 ex.printStackTrace(); 49 } 50 } 51 } 52 53 public static void main(String[] args) throws Exception { 54 new MulticastSocketTest().init(); 55 } 56 57 }
可以用多个控制台运行这个程序,输入数据,所有的控制台都将收到数据
1. 类加载:加载、连接、初始化
1) 加载:把class文件读入内存,并为之创建一个java.lang.Class对象,由ClassLoader的loadClass()完成
2) 连接:把class的二进制数据合并到JRE中,由ClassLoader的defineClass()完成,这个方法是final方法,不能重写
a)验证:验证内部结构是否正确,确保和其他类协调一致
b)准备:为静态Field分配内存并设置默认初始值(0, null, 0.0等等)
c)解析:把类的二进制数据的符号引用替换成直接引用
3) 初始化:对静态Field进行初始化(静态块的分配内存在连接准备阶段,而赋初始值在初始化阶段,因此静态初始化块可以在Field的声明的前面)
a)假如这个类还没有被加载和连接,则程序先加载并连接改类
b)假如该类的直接父类还没有被初始化,则先初始化其直接父类
c)加入类中有初始化语句,则系统依次执行这些初始化语句
2. 静态初始化块都将被当成类的初始化语句
3. java的“宏变量”。有点类似于c的#define,仅仅是替换字面量的值而已。对于一个final型的静态Field,如果该Field的值在编译时就可以确定下来,那么这个Field相当于“宏变量”。java编译器会在编译时直接把这个Field出现的地方替换成其他值,因此即使程序使用该静态Field,也不会导致该类的初始化:
class MyTest { static { System.out.println("静态初始化块..."); } static final String compileConstant = "java大法好~"; // static final String compileConstant2 = System.currentTimeMillis() + ""; } public class CompileConstantTest { public static void main(String[] args) { System.out.println(MyTest.compileConstant); // System.out.println(MyTest.compileConstant2); } }
会发现静态初始化块没有被执行。如果final的值是在编译阶段无法确定的,比如调用方法来初始化、实例化,就不是宏变量了,取消注释会发现静态初始化块被执行了。字符串连接字符串(用+号)在编译器可以被确定,因此是宏变量
3. Class.forName()不仅加载类可以完成类加载的三个阶段
4. 一旦一个类被载入JVM中,同一个类就不会被再次载入了。一个类用其全限定类名和其累加器作为同名类,因此不同的类加载器可以加载同一个类。
5. 当JVM启动时会形成由3个类加载器组成的初始类的加载器层次结构
1)Boostrap ClassLoader:根/引导类加载器。负责加载java的核心类,在Sun的JVM中,当执行java.exe命令时,使用-Xbootclasspath选项或-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。它非常特殊,不是java.lang.ClassLoader的子类,而是有JVM自身实现的
2)ExtenSion ClassLoader:扩展类加载器。负责加载JRE的扩展目录(jre/lib/ext或java.ext.dirs系统属性指定的目录)中的jar包类。比如你可以把mysql的驱动包放在jre/lib/ext下,就不需要Class.forName()来加载注册驱动了
3)System ClassLoader:系统/应用类加载器。负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所制定的JAR包和类路径。程序可以通过ClassLoader.getSystemClassLoader()来获取。如果没有特别的指定,用户自定类加载器都以ClassLoader作为父加载器
1 public class BoostrapTest { 2 /* 3 * 如果在eclipse编译不了,那就要在classpath中添加 4 * sun/misc/Launcher和sun/misc/URLClassPath的access rule为access 5 */ 6 public static void main(String[] args) throws Exception { 7 URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); 8 for (URL url : urls) {//可以发现,加载的是jre/lib下的jar包 9 System.out.println(url.toExternalForm()); 10 } 11 } 12 13 }
如果eclipse编译报错,可以这样修改:
6. 类加载机制:
1)全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的引用和其他的Class也将有该类加载器负责载入,除非显示使用另外一个类加载器来载入。
2)父类委托:先让parent类加载器加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
3)缓存机制:缓存机制将会保证所有加载过的Class都会被混村,当程序需要使用Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对戏那个时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为什么修改了Class后必须重新启动JVM,程序所做的修改才会生效的原因
7. 类加载器之间的父子关系并不是类继承上的父子关系,而是类似于树的子节点和父节点的父子关系
8. 3个系统类加载器:
1 public class ClassLoaderPropTest { 2 /* 3 * 输出结果: 4 系统类加载器: sun.misc.Launcher$AppClassLoader@4ccbc2d3 5 file:/C:/Users/Administrator/workspace/crazyJava/bin/ 6 扩展类加载器:sun.misc.Launcher$ExtClassLoader@5563d208 7 扩展类加载器的加载路径:C:\Program Files\Java\jdk1.7.0_13\jre\lib\ext;C:\windows\Sun\Java\lib\ext 8 扩展类加载器的parent:null 9 */ 10 11 public static void main(String[] args) throws Exception { 12 ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); 13 System.out.println("系统类加载器: " + systemLoader); 14 15 Enumeration<URL> em1 = systemLoader.getResources(""); 16 while (em1.hasMoreElements()) { 17 System.out.println(em1.nextElement()); 18 } 19 ClassLoader extendsionLoader = systemLoader.getParent(); 20 System.out.println("扩展类加载器:" + extendsionLoader); 21 System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs")); 22 System.out.println("扩展类加载器的parent:" + extendsionLoader.getParent()); 23 } 24 25 }
可以看见扩展类加载器的parent是null,而不是根类加载器。这是因为根类加载器没有继承ClassLoader抽象类。但实际上,扩展类加载器的父类加载器是根类加载器,只是根类加载不是用java实现的。系统类加载器是AppClassLoader的实例,扩展类加载器是ExtClassLoader的实例,实际上这两个类都是URLClassLoader的实例。
9. 类加载器加载Class的步骤:
1)检测此Class是否载入过,如果有则直接进入第8步,否则执行第2步
2)如果父类加载器不存在(要么parent是根类加载器或本身就是根类加载器),则跳到第4步执行;如果父类加载器存在,则接着执行第3步
3)请求使用父类加载器去载入目标类,如果成功载入则跳到第8步,否则接着执行第5步
4)请求使用根类加载器来载入目标类,如果成功载入则跳到第8步,否则跳到第7步
5)当前类加载器尝试寻找Class文件(从与此ClassLoader相关的类路径寻找),如果找到则执行第6步,如果找不到则跳到第7步
6)从文件中载入Class,成功载入后跳到第8步
7)抛出ClassNotFoundException异常
8)返回对应的java.lang.Class对象
其中,第5、6步允许重写ClassLoader的findClass()方法来实现自己的载入策略,甚至重写loadClass()方法来实现自己的载入过程
10. 自定义类加载器
ClassLoader有两个关键方法:
1)loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用该方法来获取指定类对应的Class对象
2)findClass(String name): 根据名称来查找类
要实现自定义的ClassLoader,推荐重写findClass()而不是重写loadClass()方法。
loadClass()的执行步骤:
1)用findLoadedClass(String)来检查是否已经加载类,如果已经加载类则直接返回
2) 在父类加载器上调用loadClass方法。如果父类加载器为null,则使用根类加载器来加载
3)调用findClass(String) 来查找类
从上面步骤中可以看出,重写findClass方法可以避免覆盖默认类加载器的父类委托、缓冲机制两种策略,如果重写loadClass方法,则实现逻辑更为复杂。
在ClassLoader里还有一个核心方法:Class defineClass(String name, byte[] b, int off, int len): 该方法负责将指定类的字节码文件(class文件)读入字节数组byte[] b内,并把它转换为Class对象,该字节码文件可以来源于文件、网络等。defineClass方法管理JVM的许多复杂实现,它负责将字节码分析称运行时数据结构,并校验有效性等。不过不用担心,程序员无需重写该方法。事实上那个,该方法是final型,即使我们想重写也没有机会。
ClassLoader还有一个常用方法:findSystemClass(String)、getSystemClassLoader()、getParent()、findLoadedClass(String name)
自定义类加载器:
1 class Hello {}//用来做测试 2 3 public class CompileClasLoader extends ClassLoader { 4 //读取一个文件的内容 5 private byte[] getBytes(String filename) throws IOException { 6 File file = new File(filename); 7 long len = file.length(); 8 byte[] raw = new byte[(int)len]; 9 try( 10 FileInputStream fin = new FileInputStream(file); 11 ) { 12 int r = fin.read(raw); 13 if (r != len) { 14 throw new IOException("无法读取全部文件:" + r + " != " + len); 15 } 16 return raw; 17 } 18 } 19 private boolean compile(String javaFile) throws IOException { 20 System.out.println("CompileClassLoader:正在编译 " + javaFile + "..."); 21 Process p = Runtime.getRuntime().exec("javac " + javaFile); 22 try { 23 p.waitFor();//等待子进程结束 24 } catch(InterruptedException e) { 25 e.printStackTrace(); 26 } 27 int ret = p.exitValue();//获得退出值 28 return ret == 0;//编译是否成功 29 } 30 protected Class<?> findClass(String name) throws ClassNotFoundException { 31 Class clazz = null; 32 String fileStub = name.replace(".", "/"); 33 String javaFilename = fileStub + ".java"; 34 String classFilename = fileStub + ".class"; 35 File javaFile = new File(javaFilename); 36 File classFile = new File(classFilename); 37 38 if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { 39 try { 40 if (!compile(javaFilename) || !classFile.exists()) { 41 throw new ClassNotFoundException("ClassNotFoundException: " + javaFilename); 42 } 43 } catch(IOException e) { 44 e.printStackTrace(); 45 } 46 } 47 if (classFile.exists()) { 48 try { 49 byte[] raw = getBytes(classFilename); 50 clazz = defineClass(name, raw, 0, raw.length); 51 } catch(IOException e) { 52 e.printStackTrace(); 53 } 54 } 55 if (clazz == null) { 56 throw new ClassNotFoundException(name); 57 } 58 return clazz; 59 } 60 public static void main(String[] args) throws Exception { 61 String progClass = "Hello"; 62 String progClass2 = "Hello1"; 63 CompileClasLoader loader = new CompileClasLoader(); 64 System.out.println(loader.loadClass(progClass));//入口 65 System.out.println(loader.loadClass(progClass2));//入口 66 } 67 68 }
这个程序比较简单,使用自定义类加载器,可以实现如下常见功能:
1)执行代码前自动验证数字签名
2)根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译Class文件
3)根据用户寻求来动态地加载类
4)根据应用需求把其他数据以字节码的形式加载到应用中
11. URLClassLoader是系统类加载器和扩展类加载器的父类,可以从本地文件获取二进制文件来加载类,也可以从远程主机获取二进制文件来加载类。
public class URLClassLoaderTest { private static Connection conn; public static Connection getConn(String url, String user, String pass) throws Exception { if (conn == null) { URL[] urls = {new URL("file:mysql-connector-java-5.1.5-bin.jar")};//file:是从当前路径获得jar,也可以用http:和ftp: URLClassLoader myClassLoader = new URLClassLoader(urls); Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance(); Properties props = new Properties(); props.setProperty("user", user); props.setProperty("password", pass); conn = driver.connect(url, props);//这里直接通过Driver来获得连接而不是通过DriverManager获得连接 } return conn; } public static void main(String[] args) throws Exception { System.out.println(getConn("jdbc:mysql://localhost:3306/mydb", "root", "root")); } }
12. 获得Class对象的方法:
1)使用Class.forName(String)
2)调用类.class。使用这种方式比较好:代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在。程序性能更好,因为这种方式无需调用方法,所以性能更好
3)调用对象的getClass()
13. 以getDeclared开头的方法获得的是本类中显式写出的成员(不管权限),也能得到父类的public成员;而get开头的方法只能得到本类中显式写出的public成员
14. Class有很多方法:
getClasses()可以获得内部类;getDeclaringClass()可以获得外部类;getSimpleName()可以获得类的简称;getInterfaces()可以获得接口;getPackage()可以获得包的类getSuperclass()可以获得父类;getAnnotations()可以获得注解;isAnonymousClass()可以判断是否是匿名类;isArray()可以判断是否是数组类;isEnum();isInterface();isInstance(Object);isAnnotation();getModifiers()可以获得权限码,可以用Modifier.toString(int)解码得到权限字符串
15. clazz.newInstance()调用的是默认构造器,要想调用其他的构造器就得先用getConstructor(Class ... clazz);获得构造器再newInstance(Object ... args);注意普通类型也是有Class的,比如int.Class,可以这样写:Class<Integer> c = int.class; 只不过基本类型的Class除了名字其他什么内容都没有
16. 加载内部类的方法:Class.forName(Outter$Inner), 注意加包名
17. 要查看设置private属性,可以先setAccessible(true)。但这个方法不只属于Field,而属于它的父类AccessibleObject,因此Method、Construtctor也可以使用。用基本类型给Field赋值可以使用setXxx(xxx),如setInt(int),如果是对象只用set(Object);获得Field的值可以用getXxx(Xxx)和get();
18. 操作数组
int[]、String[]这些数组也是引用类型的,因此也有反射的类:Array
1 public class ArrayTest { 2 3 public static void main(String[] args) throws Exception { 4 try { 5 Object arr = Array.newInstance(String.class, 10);//创建1维String数组,长度为10 6 Array.set(arr, 5, "java大法好~"); 7 Array.set(arr, 6, "asdfdsfadsf"); 8 System.out.println(Array.get(arr, 5)); 9 10 11 arr = Array.newInstance(String.class, 3, 4, 10);//锯齿String数组 12 Object arrObj = Array.get(arr, 2); 13 Array.set(arrObj, 2, new String[] {"1", "2", "3"}); 14 String[][][] cast = (String[][][])arr; 15 System.out.println(cast[2][2][0]); 16 System.out.println(cast[2][2][1]); 17 System.out.println(cast[2][2][2]); 18 } catch(Exception e) { 19 e.printStackTrace(); 20 } 21 } 22 }
Array有Class,甚至Class也有Class
19. 动态代理和AOP
这里用到了java.lang.reflect.Proxy和InvocationHandler
这个Proxy和net包中的代理服务器Proxy不一样。在这里代表的是代理类,InvocationHandler代表“执行代理类的方法时指定执行另一个方法的类", 也就是说,执行Proxy的方法时,会调用InvocationHandler中的方法:invoke
Proxy.getProxyClass(ClassLoader loader, Class<?> ... interfaces),可以创建一个Proxy的Class对象
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);可以创建一个实现interfaces的代理对象
1 interface Person { 2 void walk(); 3 int sayHello(String name); 4 } 5 6 class Before{ 7 public static void run() { 8 System.out.println("before"); 9 } 10 } 11 12 class After { 13 public static void run() { 14 System.out.println("after"); 15 } 16 } 17 18 class MyInvocationHandler implements InvocationHandler { 19 Object target; 20 public MyInvocationHandler(Object taget) { 21 this.target = taget; 22 } 23 @Override 24 public Object invoke(Object proxy, Method method, Object[] args) 25 throws Throwable { 26 Before.run(); 27 Object result = method.invoke(target, args);//别执行代理类的method方法,而是执行实际的实现类的方法 28 After.run(); 29 30 return result; 31 } 32 33 } 34 35 public class ProxyTest { 36 37 public static void main(String[] args) throws Exception { 38 Person realP = new Person() { 39 @Override 40 public void walk() { 41 System.out.println("walk()"); 42 } 43 @Override 44 public int sayHello(String name) { 45 System.out.println("sayHello(" + name + ")"); 46 return 100; 47 } 48 49 }; 50 InvocationHandler handler = new MyInvocationHandler(realP); 51 //第二个参数必须是接口 52 Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler); 53 System.out.println("p == null? : " + (p == null));//别直接输出p,因为会调用p.toString(),而该方法会返回null 54 p.walk(); 55 System.out.println("\n\n"); 56 System.out.println(p.sayHello("java")); 57 } 58 59 }
实际上,绑定InvocationHandler的代码应该写在xml中,获得代理类的具体代码和解析xml的代码应该由框架去做。与Spring的AOP不同的是,这里的要实现AOP的类必须实现一个接口,与接口耦合了;而Spring的AOP(AspectJ的AOP)是通过CGLIG修改字节码去继承我们写的类而实现的,不跟任何类耦合
20. 使用反射来获取泛型信息
1 public class GenericTest { 2 private Map<String, Integer> score; 3 private int num; 4 public static void show(Type type) { 5 if (type instanceof ParameterizedType) {//如果有参数类型,比如泛型 6 ParameterizedType pType = (ParameterizedType)type; 7 System.out.println("#########" + pType.getRawType());//原始类型,去掉泛型的类型 8 Type[] tArgs = pType.getActualTypeArguments();//获得所有参数的类型 9 System.out.println("泛型类型是:"); 10 for (int i = 0; i < tArgs.length; ++i) { 11 System.out.println("第" + i + "个泛型类型是:" + tArgs[i]); 12 } 13 } else { 14 System.out.println(type); 15 } 16 17 } 18 public static void main(String[] args) throws Exception { 19 Class<GenericTest> clazz = GenericTest.class; 20 Field f = clazz.getDeclaredField("score"); 21 Class<?> a = f.getType(); 22 System.out.println("score的类型:" + a); 23 24 Type genericType = f.getGenericType();//获得泛型类型 25 show(genericType); 26 System.out.println("\n----------------------"); 27 f = clazz.getDeclaredField("num"); 28 a = f.getType(); 29 System.out.println("score的类型:" + a); 30 31 genericType = f.getGenericType();//获得泛型类型 32 show(genericType); 33 } 34 35 }
Type是所有类型的父接口,包括原始类型、参数化类型、数组类型、基本类型
8/6 更新完毕