JDK APIjavascript
什么是 JDK APIphp
API文档是咱们用来了解JDK中提供的类库,咱们能够先经过索引输入并找到咱们须要了解的类,然后咱们就能够方便的了解该类的做用,常量的做用,以及该类提供的全部方法的做用,以及方法的参数及返回值的含义。java
JDK包结构c++
文档注释规范程序员
文档注释正则表达式
经过注释提升Java源程序代码的可读性;使得Java程序条理清晰,易于区分代码行与注释行。另外一般在程序开头加入做者,时间,版本,要实现的功能等内容注释,方便后来的维护以及程序员的交流。objective-c
文档注释规范算法
Javadoc命令生成文档编程
String及其常见APIc#
String是不可变对象
因为字符串在实际开发中被普遍使用,那么在频繁使用某个字符串时,会出现频繁建立一个字符串对象的现象,java为此对字符串的使用采用了一个优化措施,使得String对象为不可变对象,一旦在内存中建立,内容不能发生变化,若要对字符串内容改变,那么就会建立新对象。这样作的目的是能够最大程度的重用相同内容的字符串以减少系统资源的开销。
String常量池
JVM对字符串有一个限制,让字符串做为不变对象,这样就能够作到重用。事实上,当咱们经过字面量,常量来初始化一个字符串时,JVM首先会从字符串的常量池(一个JVM内部维护的内存区域,用来保存已经建立过的字符串对象)中查询用来保存该字符串的对象是否存在,若存在则直接引用,若不存在则建立该字符串对象并存入常量池,而后引用它。由于字符串内容不能改变,因此咱们能够放心的重用他们。
内存编码及长度
使用indexOf实现检索
int indexOf(int ch):用来检查给定的一个字符在当前字符串中第一次出现的下标位置。这里的下标和数组的下标意思相近,0表示该字符串的第1个字符,以此类推。当该字符串中并不包含给定的字符时,那么该方法返回-1。 例如:
String str = "HelloWorld"; System.out.println(str.indexOf('W'));//5 System.out.println(str.indexOf('h'))//-1
使用substring获取子串
String substring(int begin,int end):用来截取当前字符串的部份内容以获取这个子字符串。咱们只须要传入两个整数,一个用来表示从哪里开始,另外一个用来表示截取到哪里。这里的位置要使用字符串的下标来表示,而且要注意,这两个数字表示的范围是“含头不含尾的”,换句话说就是包含开始下标的字符,可是不包含结束下标的字符。
trim
String trim():将字符串两边的空白(空白有不少种,空格是其中之一)去除掉,并将去除后的新字符串返回给咱们。
charAt
char charAt(int index):用于给定一个下标位置,来获取该字符串中这个位置的字符。
startsWith和endsWith
boolean startsWith(String suffix):用来判断当前字符串是不是以给定的字符串开始的。这里要注意大小写是敏感的。
boolean endsWith(String suffix):用来判断当前字符串是不是以给定的字符串结尾的。
例如咱们可使用endsWith()就能够根据一个文件的名字来判断它是不是以".jpg",".gif"等字符串结尾来得知该文件是否为图片。
String str = "java.jpg"; if(str.endsWith(".jpg")){ System.out.println("是一张图片"); }else{ System.out.println("不是一张图片"); }
大小写变换
valueOf
字符串提供了不少重载的valueOf()方法,能够将其余基本类型的值以字符串的形式描述。
static String valueOf(int i): 返回 int 参数的字符串表示形式 static String valueOf(boolean b): 返回 boolean 参数的字符串表示形式 static String valueOf(char c): 返回 char 参数的字符串表示形式 static String valueOf(double d): 返回 double 参数的字符串表示形式 static String valueOf(char[] c): 返回 char 数组参数的字符串表示形式 static String valueOf(char[] c,int offset,int count): 返回 char 数组参数的特定子数组的字符串表示形式。 static String valueOf(float): 返回 float 参数的字符串表示形式 static String valueOf(long l): 返回 long 参数的字符串表示形式 static String valueOf(Object o): 返回 Object 参数的字符串表示形式
StringBuilder及其经常使用API
StringBuilder封装可变字符串
StringBuilder经常使用方法
append(String str):追加字符串; insert (int dstOffset,String s):插入字符串; delete(int start,int end):删除字符串; replace(int start,int end,String str): 替换字符串; reverse():字符串反转。
StringBuilder
StringBuilder的不少方法的返回值均为StringBuilder类型。这些方法的返回语句均为:return this。也就是能够作链式调用
因为改变封装的字符序列后又返回了该对象的引用。
buf.append("ibm").append("java").insert(3, "oracle").replace(9, 13, "JAVA"); System.out.println(buf.toString());
StringBuilder 总结
StringBuilder是可变字符串。字符串的内容计算,建议采用StringBuilder实现,这样性能会好一些。
java的字符串链接的过程是利用StringBuilder实现的,代码以下所示:
String s = "AB"; String s1 = s + "DE"+1; String s1 = new StringBuilder(s).append("DE").append(1).toString();
StringBuffer 和StringBuilder的区别:
StringBuffer是线程安全的,同步处理的,性能稍慢; StringBuilder是非线程安全的,并发处理的,性能稍快。
基本正则表达式
正则表达式简介
所谓正则表达式就是使用一系列预约义的特殊字符来描述一个字符串的格式规则,而后使用该格式规则匹配某个字符串是否符合格式要求。
一、“.”和"\" "."点儿,在正则表达式中表示任意一个字符。 "\"在正则表达式中是转意字符,当咱们须要描述一个已经被正则表达式使用的特殊字符时,咱们就能够经过使用"\"将其转变为本来的意思。 "\"在正则表达式中也有一些预约义的特殊内容: \d:表示任意一个数字 \w:表示任意一个单词字符(只能是 数字,字母,下划线) \s:表示任意一个空白字符(\t \r \n \f \x0B) \D:表示任意一个非数字字符 \W:表示任意一个非单词字符 \S:表示任意一个非空白字符 二、"字符集合 []" "[]"用来描述单一字符,方括号内部能够定义这个字符的内容,也能够描述一个范围。例如: [abc]:表示该字符只能是a或者b或者c [123]:表示该字符只能是1或者2或者3 当咱们须要描述全部小写字母时,咱们可使用范围 [a-z],表示该字符能够是任意一个小写字母。 一样还可使用 [0-9] 来表示该字符能够是任意一个数字。 也能够在多个范围内选择。好比,[a-zA-Z0-9_] 表示该字符能够是任意字母,数字以及"下划线"。 三、"*"、"+"、"?" 一般咱们须要描述的字符串会有不少重复出现的元素,但又不须要严格限制出现的次数时,咱们就可使用"*","+"这些量词。 例如:邮箱地址,那么在"@"字符前容许出现若干字符做为用户名。这时候咱们就可使用"\w+"来描述这里至少出现一个单词字符了。 "+":表示内容能够连续出现至少1次以上 "*":表示内容出现0-若干次 "?":表示内容出现0-1次 四、{n}、{n,}{n,m} 除了前面讲到的量词外,有时咱们也须要要求内容出现的次数有具体要求。好比手机号码。 这时咱们要求出现的数字就不能是一个模糊的概念了,而必需要求11位。 又好比咱们要求用户输入密码时,要求密码是6-15位。遇到这类问题是,咱们可使用: {n}:表示内容必须出现n次 {n,m}:表示内容出现n-m次 {n,}:表示内容出现至少n次 例如,\d{11} 就表示数字只能出现11位,这样就解决了上述的问题。
分组
经过上面的内容,咱们还没法解决相似下面的问题: 在描述电话号码时,前面有区号,区号的形式能够是0086或者+86 那么咱们如何在这两个字符串之间选择? 这时咱们可使用分组"()"。() 能够将内容看作一个总体,()中可使用"|"来表示或关系。例如,(+86|0086) 表示这里能够是+86或者0086。
"^"和"$"
经过在正则表达式的开始添加"^"以及末尾添加"$"来表示一个总体。若不使用它们,那么正则表达式只匹配某个字符串的部份内容是否符合格式规则, 但使用它们,则要求字符串必须从头至尾都知足该格式规则。 例如,^\w{ 8,10 }$ 表示总体字符串只能出现单词字符8-10个。
String正则相关API
matches方法
matches()方法的参数要求咱们传入一个用字符串描述的正则表达式,而后使用该正则表达式描述的字符串格式规则来匹配当前字符串,若知足那么该方法返回true。不然返回false。 例如:
String emailRegEx = "^[a-zA-Z0-9_.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9]{2,4}$"; String email = "bjliyi@tarena.com.cn"; System.out.println(email.matches(emailRegEx));//true
split方法
String[] split(String regex):参数要求传入一个用字符串描述的正则表达式,而后使用该正则表达式描述的字符串规则来匹配当前字符串,并按照知足的部分将字符串拆分。 例如:
String str = "java,c#,php,javascript"; String[] array = str.split(","); //[java,c#,php,javascript] System.out.println(Arrays.toString(array));
replaceAll方法
String replaceAll(String regex,String replacement):参数要求传入一个用字符串描述的正则表达式和一个须要替换的字符串,而后使用该正则表达式描述的字符串规则来匹配当前字符串,并将知足的部分替换为须要替换的这个字符串。 例如:
String str = "abc123bcd45ef6g7890";; str = str.replaceAll("\\d+", "数字"); System.out.println(str);//abc数字bcd数字ef数字g数字
Object
Object
Object类是java中全部类的顶级父类。若咱们定义的一个类没有显式的使用extends继承某个类时,默认就是继承自Object的。
toString()方法
1. 如何重写toString方法 1. 既然Object是全部类的顶级父类,那么在Object中定义的方法全部的类都具有。其中之一就是toStirng()方法。 2. String toString():该方法java但愿咱们重写时返回一个字符串,这个字符串的原则为:用一个字符串来描述当前对象。 3. Object实现了toString()方法,返回的是当前对象的“句柄”。 格式为:类的彻底限定名@hashcode。 由于Object实现的toString()方法不具有什么实际开发意义,因此若咱们须要在子类中使用该方法时一般咱们会重写它。 2. String类重写toString() public String toString(){ return this; } 从源码中咱们能够看到,String重写了Object的toString()方法,该方法直接将当前字符串对象自身返回。
equals()方法
equals方法
boolean equals():该方法java但愿咱们重写时返回一个boolean值,表示两个对象间的内容比较是否一致。 Object已经实现了该方法,代码以下:
public boolean equals (Object obj) { return (this == obj); }
由此看出,实际上Object中重写该方法依旧使用"=="比较,因此当咱们在子类中须要比较对象内容时就要重写该方法。
如何重写equals方法
重写equals方法应遵循几个规则:
1. 任何对象与null比较都应返回false 2. 两个对象不属于同一个类时应返回false 3. 同一个对象equals比较应当恒等为true
那么除此以外,两个对象在比较时,应根据具体的业务需求来自行决定对象的哪些属性相同时对象内容相同。
String重写equals()方法
String重写了equals方法,做用是比较两个字符串对象中保存的字符序列是否彻底一致。
equals与 == 的区别
"=="是值比较,对于引用类型变量而言,该变量保存的是对象的地址,因此使用"=="比较时,意思为两个变量的地址是否相等, 换句话说就是看两个变量引用的是否为同一个对象 equals是内容比较,对于两个引用变量而言,是比较两个变量所引用的对象内容是否相同。 举个例子, 就好像一对双胞胎,他们是两个独立的个体,是两个对象。 因此那么用"=="比较是 false。可是由于他们“长得同样”,因此equals方法比较是true。 咱们也能够变相的理解为:"=="是判断是否为同一个,而"equals"是判断像不像。
包装类概述
用于解决基本类型不能参与面向对象开发的问题。 包装类能够将基本类型以对象的形式存在,从而 就具备了面向对象的相关特性。 数字类型包装类继承自Number类,能够在6中数字 类型之间转换。
8个基本类型包装类
Number及其主要方法
上一节咱们已经知道,除了Character与Boolean以外的其余包装类都是继承自Number的,这些包装类都有一个共性,描述的都是数字。 那么咱们来了解一下他们的父类:java.lang.Number Number是一个抽象类。自己不能实例化。Number 的子类必须提供将表示的数值转换为 byte、double、float、int、long 和 short 的方法 好比: abstract double doubleValue() 以double形式返回指定的数值 abstract int intValue() 以int形式返回指定的数值 abstract float floatValue() 以float形式返回指定的数值 剩下的抽象方法请参阅API文档:java.lang.Number 。
Integer经常使用功能
java.lang.Integer是int的包装类,其每个实例用于描述一个基本类型int的值。 Integer有一个静态方法static int parseInt(String s)。 该方法的做用是将一个描述整数的字符串解析为该整数,并用int形式返回。该方法可能会抛出NumberFormatException异常: 当给定的字符串里边含有非整数字符时。
Double经常使用功能
java.lang.Double是double的包装类,其每个实例用于描述一个基本类型double的值。 Double有一个静态方法static double parseDouble(String s)。 该方法的做用是将一个描述小数的字符串解析为该小数,并用double形式返回。 该方法可能会抛出NumberFormatException异常: 若是字符串不包含可解析的 double 值。
自动装箱和拆箱操做
java 5.0以后推出的一个新的特性
public static void main(String[] args) { /* * 自动拆装箱不是JVM承认的,而是编译器 * 承认的。 * * 当编译器在编译下面代码时,会自动添加 * 代码将基本类型转换为引用类型,因此在 * 编译后的class文件中,下面代码的样子 * 是: * Integer i = Integer.valueOf(1); */ Integer i = 1; /* * 一样的,下面代码在编译后的class文件 * 中的样子: * int ii = i.intValue(); */ int ii = i; }
Date及其经常使用API
JAVA 中的时间
Date类简介
java.util.Date 类封装日期及时间信息。
Date类的大多数用于进行时间份量计算的方法已经被Calendar取代。
由于Date的设计具备"千年虫"以及"时区"的问题,因此Date中的大部分方法已经不建议使用了,它们都被java.util.Calendar类所取代
setTime与getTime方法
void setTime(long time):
该方法用于为一个Date对象设置其须要表示的时间,该参数为一个long值,其含义是须要表示的这个时间点距离1970年1月1日 00:00:00之间的毫秒差。
long getTime()
该方法用于获取一个Date对象所表示的时间点,该返回值为一个long值,表示该时间点距离1970年1月1日 00:00:00之间的毫秒差。
Date 重写 toString方法
Date重写了toString()方法,用一个字符串来描述当前Date对象所表示的时间。 格式以下:
Mon Feb 17 15:36:55 CST 2014
由此咱们能够看出,实际上Date的toString()方法返回的字符串虽然很清晰的描述了时间,可是对于非英语地区来说,该字符串不够友好,咱们更但愿按照特定地区表示时间的方式。好比咱们更习惯如下的风格:
2014-02-17 15:36:55 星期一
那么有没有方式能够代替 Date的toString()方法来获取一个特定格式的字符串呢?答案是确定的,java为咱们提供了一个类,叫作SimpleDateFormat,该类就能够完成。
SimpleDateFormat
SimpleDateFormat简介
SimpleDateFormat 是一个以与语言环境有关的方式来格式化和解析日期的具体类。它容许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。
简单的说,SimpleDateFormat就是根据一个特定的日期格式在字符串与Date之间相互转换。
日期模式匹配字符串
日期模式的匹配字符串如表所示。
例如: yyyy年MM月dd日--HH:mm:ss 能够匹配 2014年01月06日--13:22:41
将Date格式化为String
将Date格式化为String,咱们须要使用SimpleDateFormat提供的方法:
String format(Date d)
例如:
Date now = new Date();//默认实例化的Date表示当前系统时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String str = sdf.format(now); System.out.println(str);//2014-01-06 13:21:12
将String解析为Date
将String格式化为Date,咱们须要使用SimpleDateFormat提供的方法:
Date parse(String s)
例如:
String str = "2008年08月08日 12:22:46"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); Date date = sdf.parse(str); System.out.println(date);// Fri Aug 08 12:22:46 CST 2008
Calendar类
Calendar简介
getInstance()方法
设置日期及时间份量
Calendar提供了一种通用的设置时间的方式:
void set(int field,int value)
该方法能够经过对不一样的时间份量分别设置不一样的值。Calendar对不一样的时间份量提供了相应的常量,咱们在使用set方法设置时,第一个参数就应当使用对应的常量做为时间份量。
Calendar calendar = Calendar.getInstance();//建立出的Calendar表示当前系统时间 //设置年为2008年 calendar.set(Calendar.YEAR,2008); //设置月为5月 calendar.set(Calendar.Month,4);//月份从0开始 calendar.set(Calendar.Month,Calendar.MAY);//也可使用常量来设置 //设置日为30日 caneldar.set(Calendar.DAY_OF_MONTH,30);
获取时间份量对应的值
Calendar提供了一种通用的获取时间份量的方式:
int get(int field)
该方法能够经过对不一样的时间份量获取相应的值。Calendar对不一样的时间份量提供了相应的常量,咱们在使用get方法获取时,参数就应当使用对应的常量做为时间份量。
Calendar calendar = Calendar.getInstance(); int year = calendar.get(Calendar.YEAR); int month = calednar.get(Calendar.Month); int date = calendar.get(Calendar.DAY_OF_MONTH); //须要注意,月份要加1,由于月份是从0开始的 System.out.println(year+"-"+(month+1)+"-"+date);//2014-1-4
getActualMaximum方法
int getActualMaximum(int field)方法用于获取给定时间份量所容许的最大值
例如:
获取当前系统时间中当月的最后一天(日所容许的最大值)
Calendar calendar = Calendar.getInstance(); int max = calendar. getActualMaximum(Calendar.DAY_OF_MONTH); System.out.println("当前月的最后一天为:"+max+"日");//当前月的最后一天为31日
add方法
Calendar还容许咱们使用统一的方式来对某个时间份量的值进行计算。咱们须要使用方法 void add(int field,int amount)。该方法能够为某个时间份量的值加上给定的值,若想减去给定的值,那么传入的值须要是负数便可。而且计算后会自动进行相应的进位,例如若当前为月底,那么加一天后,为下个月的月初,而月就会进位。
例如: 当前系统时间为 2014-01-31日
Calendar calendar = Calendar.getInstance(); //计算明天(在日的基础上加一天) calendar.add(Calendar.DAY_OF_YEAR,1);//当前Calendar表示的为2014-02-01,月进位了
setTime与getTime方法
Calendar的void setTime(Date date),容许咱们为Calendar设置Date对象所表示的时间。
Calendar的 Date getTime(),容许咱们获取一个使用Date对象描述的Calendar所表示的时间。
例如:
Calendar calendar = Calendar.getInstance(); Date date = calendar.getTime(); System.out.println(date);// Mon Feb 17 15:36:55 CST 2014
Calendar与Date之间的相互转换
/* * 默认建立出来的是阳历历法: * GregorianCalendar * * 默认就表示当前系统时间 */ Calendar calendar = Calendar.getInstance(); /* * Calendar的toString不能直观反映 * 表示的具体日期 */ System.out.println(calendar); /* * * Calendar提供了与Date之间互转的相关 * 方法: * Calendar->Date * * Date getTime()方法 * 该方法会返回一个Date对象,该对象表示 * 的时间就是当前Calendar表示的时间。 */ Date date = calendar.getTime(); System.out.println(date); /* * Date -> Calendar * void setTime(Date date) * 该方法容许当前Calendar表示给定的 * Date所表示的时间 */ calendar.setTime(date);
Collection
java提供了一种能够存数一组数据的数据结构,其提供了丰富的方法,在实际开发中每每比数组使用的普遍。这种数据结构成为集合:Collection。 Collection是一个接口,其定义了集合的相关功能方法。
List和Set
Collection派生出了两个子接口,一个是List另外一个则是Set。 List:称为可重复集,顾名思义,该集合中是容许存放重复元素的,那么何为重复元素? 重复元素指的并不是是同一个元素,而是指equals方法比较为true的元素。 Set:称为不可重复集,因此,该集合中是不能将相同的元素存入集合两次,同List, 这里相同指的也是两个元素的equals比较结果为true。
集合持有对象的引用
集合中存储的都是引用类型的元素,那么引用类型变量实际上存储的是对象的“地址”,因此实际上集合只存储了元素对象在堆中的地址。而并非将对象自己存入了集合中。
add()方法
Collection定义了一个add方法用于向集合中添加新元素。
contains方法
该方法会用于判断给定的元素是否被包含在集合中。若包含则返回true,不然返回false。
这里须要注意的是,集合在判断元素是否被包含在集合中是使用元素的equals的比较结果。
(o==null ? e==null : o.equals(e)) 其中e是集合中的元素。
size,clear,isEmpty方法
size方法用于获取当前集合中的元素总数。该方法定义为:int size()
clear方法用于清空集合。该方法定义为:void clear()
isEmpty方法用于判断当前集合中是否不 包含元素。该方法定义为:boolean isEmpty()
addAll与containsAll方法
addAll方法用于将给定集合中的全部元素添加到当前集合中
containsAll方法用于判断当前集合是否包含给定集合中的全部元素,若包含则返回true。
Iterator 迭代器
Collection提供了一个遍历集合的通用方式,迭代器(Iterator)。 获取迭代器的方式是使用Collection定义的方法: Iterator iterator() 迭代器Iterator是一个接口,集合在覆盖Collection的iterator()方法时提供了迭代器的实现。 Iterator提供了统一的遍历集合元素的方式。
hasNext与next方法
迭代器用于遍历集合的两个主要方法: boolean hasNext():判断集合是否还有元素能够遍历。 E next():返回迭代的下一个元素 遍历集合应遵循“先问后取”的方式,也就是说,应当在肯定hasNext()方法的返回值为true的状况下再经过next()方法取元素。 由此能够看出,使用迭代器遍历集合是经过boolean值驱动的,因此它更适合使用while循环来遍历。 例如: Collection<String> c = new HashSet<String>(); c.add("java"); c.add("cpp"); c.add("php"); c.add("c#"); c.add("objective-c"); Iterator<String> it = c.iterator(); while (it.hasNext()) { String str = it.next(); System.out.println(str); }
remove方法
Collection c = new ArrayList(); c.add("one"); c.add("#"); c.add("two"); c.add("#"); c.add("three"); c.add("#"); c.add("four"); /* * Iterator iterator() * 该方法会返回一个Iterator的实现类:迭代器 * 集合遍历元素使用的就是该统一的方式。 * 迭代器遍历集合遵循: * 问,取,删。 * 其中删除元素操做不是必须的。 */ Iterator it = c.iterator(); /* * boolean hasNext() * 经过迭代器询问集合是否还有元素 * 能够取出。 */ while(it.hasNext()){ /* * E next() * 从集合中取出下一个元素 */ String str = (String)it.next(); System.out.println(str); //从集合中删除"#" if("#".equals(str)){ /* * 在使用迭代器遍历集合的过程当中 * 不能经过集合的方法修改集合元素 * 数量,不然迭代器可能会抛出异常。 */ // c.remove(str); /* * void remove() * 迭代器提供的remove方法用于 * 从集合中删除经过next()方法 * 取出来的元素。 */ it.remove(); } }
加强for循环
Java5.0以后推出了一个新的特性,加强for循环,也称为新循环。该循环不通用于传统循环的工做,其只用于便利集合或数组。 语法:
for(元素类型 e : 集合或数组){ 循环体 }
新循环并不是新的语法,而是在编译过程当中,编译器会将新循环转换为迭代器模式。因此新循环本质上是迭代器。 例如:
Collection<String> c = new HashSet<String>(); c.add("java"); c.add("cpp"); c.add("php"); c.add("c#"); c.add("objective-c"); for (String str : c) { System.out.print(str.toUpperCase() + " "); } // CPP PHP C# JAVA OBJECTIVE-C
泛型机制
泛型在集合中的应用
泛型是Java SE 5.0引入的特性,泛型的本质是参数化类型。在类、接口和方法的定义过程当中,所操做的数据类型被传入的参数指定。 Java泛型机制普遍的应用在集合框架中。全部的集合类型都带有泛型参数,这样在建立集合时能够指定放入集合中的对象类型。Java编译器能够据此进行类型检查,这样能够减小代码在运行时出现错误的可能性。 咱们来举个例子,好比ArrayList,其在定义时是这样的:
public class ArrayList<E> { … … … public boolean add(E e) {…}; public E get(int index) {…}; }
由此咱们能够看出,再声明ArrayList时,类名的右侧有一个<E>。"<>"表示泛型,而其中可使用数字字母下划线(数字不能的第一个字符)来表示泛型的名字。(一般咱们使用一个大写字母来表示,固然这个不是规定。)这时,在类中声明的方法的参数,返回值类型能够被定义为泛型。这样在建立对象时能够将类型做为参数传递,此时,类定义全部的E将被替换成传入的参数。 例如:
ArrayList<String> list = new ArrayList<String>();//泛型E在这里被指定为String类型 list.add("One");//那么add方法的参数就被替换为String类型 list.add(100);//这里就会出现编译错误,由于这里的参数应为String类型。
List
List接口是Collection的子接口,用于定义线性表数据结构;能够将List理解为存放对象的数组,只不过其元素个数能够动态的增长或减小。而且List是可重复集
ArrayList和LinkedList
List接口的两个常见实现类为ArrayList和LinkedList,分别用动态数组和链表的方式实现了List接口。
能够认为ArrayList和LinkedList的方法在逻辑上彻底同样,只是在性能上有必定的差异,ArrayList更适合于随机访问而LinkedList更适合于插入和删除;在性能要求不是特别苛刻的情形下能够忽略这个差异。
get与set方法
List除了继承Collection定义的方法外,还根据其线性表的数据结构定义了一系列方法,其中最经常使用的就是基于下标的get和set方法。
E get(int index):获取集合中指定下标对应的元素,下标从0开始。
E set(int index, E elment):将给定的元素存入给定位置,并将原位置的元素返回。
例如:
List<String> list = new ArrayList<String>(); list.add("java"); list.add("cpp"); list.add("php"); list.add("c#"); list.add("objective-c"); // get方法遍历List for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } String value = list.set(1, "c++"); System.out.println(value); // cpp System.out.println(list); // [java, c++, php, c#, objective-c] // 交换位置1和3上的元素 list.set(1, list.set(3, list.get(1))); System.out.println(list); // [java, c#, php, c++, objective-c]
插入和删除
List根据下标的操做还支持插入与删除操做:
void add(int index,E element):
将给定的元素插入到指定位置,原位置及后续元素都顺序向后移动。
E remove(int index):
删除给定位置的元素,并将被删除的元素返回。
例如:
List<String> list = new ArrayList<String>(); list.add("java"); list.add("c#"); System.out.println(list); // [java, c#] list.add(1, "cpp"); System.out.println(list); // [java, cpp, c#] list.remove(2); System.out.println(list); // [java, cpp]
subList方法
List的subList方法用于获取子List。
须要注意的是,subList获取的List与原List占有相同的存储空间,对子List的操做会影响的原List。
List<E> subList(int fromIndex, int toIndex);
fromIndex和toIndex是截取子List的首尾下标(前包括,后不包括) 。
例如:
List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < 10; i++) { list.add(i); } System.out.println(list); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] List<Integer> subList = list.subList(3, 8); System.out.println(subList); // [3, 4, 5, 6, 7] // subList得到的List和源List占有相同的数据空间 for (int i = 0; i < subList.size(); i++) { subList.set(i, subList.get(i) * 10); } System.out.println(subList); // [30, 40, 50, 60, 70] System.out.println(list); // [0, 1, 2, 30, 40, 50, 60, 70, 8, 9] // 能够用于删除连续元素list.subList(3, 8).clear(); System.out.println(list);
List转换为数组
List的toArray方法用于将集合转换为数组。但实际上该方法是在Collection中定义的,因此全部的集合都具有这个功能。
Collection<String> c = new ArrayList<String>(); c.add("one"); c.add("two"); c.add("three"); c.add("four"); //Object[] array = c.toArray(); String[] array = c.toArray(new String[c.size()]); System.out.println(array.length); System.out.println(Arrays.toString(array));
数组转换为List
Arrays类中提供了一个静态方法asList,使用该方法咱们能够将一个数组转换为对应的List集合。 其方法定义为:
static <T>List<T> asList<T… a>
返回的List的集合元素类型由传入的数组的元素类型决定。
须要注意的是,返回的集合咱们不能对其增删元素,不然会抛出异常。而且对集合的元素进行的修改会影响数组对应的元素。 例如:
String[] strArr = { "a", "b", "c" }; List<String> list = Arrays.asList(strArr); System.out.println(list); // [a, b, c] // list.add("d"); // 会抛出UnsupportedOperationException // java.util.Arrays$ArrayList System.out.println(list.getClass().getName()); List<String> list1 = new ArrayList<String>(); list1.addAll(Arrays.asList(strArr));
示例代码:
String[] array = {"one","two","three","four"}; for(int i=0;i<array.length;i++){ System.out.println(array[i]); } List<String> list = Arrays.asList(array); System.out.println(list); list.set(0, "1"); System.out.println(list); //修改集合元素就是修改数组对应元素 for(int i=0;i<array.length;i++){ System.out.println(array[i]); } //数组转换的集合不容许添加新元素 // list.add("five"); /* * 如有修改元素数量需求时,能够自行建立 * 一个集合 */ List<String> list1 = new ArrayList<String>(list); // list1.addAll(list); list1.add("five"); System.out.println(list1); // Set<String> set // = new HashSet<String>(list1); // System.out.println(set);
List排序
Collections.sort方法实现排序
Collections是集合的工具类,它提供了不少便于咱们操做集合的方法,其中就有用于集合排序的sort方法。该方法的定义为:
void sort(List<T> list)
其做用是对集合元素进行天然排序(按照元素的由小至大的顺序) 例如:
List<Integer> list = new ArrayList<Integer>(); Random r = new Random(1); for (int i = 0; i < 10; i++) { list.add(r.nextInt(100)); } System.out.println(list); // [85, 88, 47, 13, 54, 4, 34, 6, 78, 48] Collections.sort(list); System.out.println(list); // [4, 6, 13, 34, 47, 48, 54, 78, 85, 88]
Comparable
经过上一节咱们知道了如何对集合元素进行天然排序,可是要想对元素进行天然排序那么就必需要有一个必要条件,就是元素的大小。集合中存入的都是引用类型,是以对象的形式存在于内存中,那么对象是如何进行的大小比较呢?实际上,若想对某个集合的元素进行天然排序,该集合的元素有一个要求,就是这些元素必须是Comparable的子类。
Comparable是一个接口,用于定义其子类是能够比较的。由于该接口有一个抽象方法:
int compareTo(T t) 全部子类都须要重写该方法来定义对象间的比较规则。该方法要求返回一个整数,这个整数不关心具体的值,而是关注取值范围。 当返回值>0时,表示当前对象比参数给定的对象大。 当返回值<0时,表示当前对象比参数给定的对象小。 当返回值=0时,表示当前对象和参数给定的对象相等。 例如: Class Cell implements Comparable<Cell>{ int row; int col; public Cell(int row,int col){ this.row = row; this.col = col; } public int compareTo(Cell c){ //根据row比较大小 return this.row - c.row; } }
那么Collections的sort在进行排序时就会根据集合中元素的compareTo方法的返回值来判断大小从而进行天然排序。
// Cell实现了Comparable接口,CompareTo方法逻辑为按照row值的大小排序 List<Cell> cells = new ArrayList<Cell>(); cells.add(new Cell(2, 3)); cells.add(new Cell(5, 1)); cells.add(new Cell(3, 2)); Collections.sort(cells); System.out.println(cells); // [(2,3), (3,2), (5,1)]
Comparator
一旦Java类实现了Comparable,其比较逻辑就已经肯定;若是但愿在排序的操做中临时指定比较规则,能够采用Comparator接口回调的方式。
该接口要求实现类必须重写其定义的方法:
int compare(T o1,T o2)
该方法的返回值要求,若o1>o2则返回值应>0,若o1<o2则返回值应<0,若o1==o2则返回值应为0 例如:
List<Cell> cells = new ArrayList<Cell>(); cells.add(new Cell(2, 3)); cells.add(new Cell(5, 1)); cells.add(new Cell(3, 2)); // 按照col值的大小排序 Collections.sort(cells, new Comparator<Cell>() { @Override public int compare(Cell o1, Cell o2) { return o1.col - o2.col;} }); System.out.println(cells); // [(5,1), (3,2), (2,3)]
队列和栈
Queue
队列(Queue)是经常使用的数据结构,能够将队列当作特殊的线性表,队列限制了对线性表的访问方式:只能从线性表的一端添加(offer)元素,从另外一端取出(poll)元素。
队列遵循先进先出(FIFO First Input First Output )的原则。
JDK中提供了Queue接口,同时使得LinkedList实现了该接口(选择LinkedList实现Queue的缘由在于Queue常常要进行插入和删除的操做,而LinkedList在这方面效率较高)。
Queue提供了操做队列的相关方法,其主要方法以下: boolean offer(E e):将元素追加到队列末尾,若添加成功则返回true。 E poll():从队首删除并返回该元素。 E peek():返回队首元素,可是不删除。
例如:
Queue<String> queue = new LinkedList<String>(); queue.offer("a"); queue.offer("b"); queue.offer("c"); System.out.println(queue); // [a, b, c] String str = queue.peek(); System.out.println(str); // a while (queue.size() > 0) { str = queue.poll(); System.out.print(str + " "); // a b c }
Deque
Deque是Queue的子接口,定义了所谓“双端队列”即从队列的两端分别能够入队(offer)和出队(poll),LinkedList实现了该接口。
若是将Deque限制为只能从一端入队和出队,则可实现“栈”(Stack)的数据结构,对于栈而言,入栈称之为push,出栈称之为pop。
栈遵循先进后出(FILO First Input Last Output )的原则。
Deque提供了操做栈的相关方法,其主要方法以下: void push(E e):将给定元素"压入"栈中。存入的元素会在栈首。即:栈的第一个元素 E pop():将栈首元素删除并返回。
例如:
Deque<String> stack = new LinkedList<String>(); stack.push("a"); stack.push("b"); stack.push("c"); System.out.println(stack); // [c, b, a] String str = stack.peek(); System.out.println(str); // c while (stack.size() > 0) { str = stack.pop(); System.out.print(str + " "); // c b a }
Map接口
Map 接口
java提供了一组能够以键值对(key-value)的形式存储数据的数据结构,这种数据结构成为Map。 咱们能够把Map当作一个多行两列的表格,其中第一列存放key,第二列存放value。 而每一行就至关于一组key-value对,表示一组数据。 Map对存入的元素有一个要求,就是key不能重复,所谓不能重复指的是在Map中不能包含两个equals为true的key。 Map对于key,value的类型没有严格要求,只要是引用类型都可。可是为了保证在使用时不会形成数据混乱,一般咱们会使用泛型去约束key与value的类型。
put方法
既然咱们知道了Map在保存数据时其实是存入了两部分信息的 ,key与value。那么咱们来看看如何向Map中存入数据。
Map提供了一个方法:
V put(K k,V v)
该方法的做用是将key-value对存入Map中,由于Map中不容许出现重复的key,因此若当次存入的key已经在Map中存在,则是替换value操做,而返回值则为被替换的元素。若此key不存在,那么返回值为null。
get方法
咱们学会了如何向Map中存入数据,那么咱们再来看看如何获取数据。Map中获取数据的方式是给定Key获取对应的Value。
Map提供了一个方法:
V get(Object key)
该方法的做用就是根据给定的key去查找Map中对应的value并返回,若当前Map中不包含给定的key,那么返回值为null。
containsKey方法
Map中的containsKey方法用于检测当前Map中是否包含给定的key。其方法定义以下:
boolean containsKey(Object key)
若当前Map中包含给定的key(这里检查是否包含是根据key的equals比较结果为依据的。)则返回true。
HashMap
hash表原理
HashMap是Map的一个经常使用的子类实现。其实使用散列算法实现的。 HashMap内部维护着一个散列数组(就是一个存放元素的数组),咱们称其为散列桶, 而当咱们向HashMap中存入一组键值对时,HashMap首先获取key这个对象的hashcode()方法的返回值, 而后使用该值进行一个散列算法,得出一个数字,这个数字就是这组键值对要存入散列数组中的下标位置。 那么得知了下标位置后,HashMap还会查看散列数组当前位置是否包含该元素。(这里要注意的是,散列数组中 每一个元素并不是是直接存储键值对的,而是存入了一个链表,这个链表中的每一个节点才是真实保存这组键值对 的。)检查是否包含该元素时根据当前要存入的key在当前散列数组对应位置中的链表里是否已经包含这个key, 若不包含则将这组键值对存入链表,不然就替换value。 那么在获取元素时,HashMap一样先根据key的hashcode值进行散列算法,找到它在散列数组中的位置, 而后遍历该位置的链表,找到该key所对应的value以后返回。 看到这里可能有个疑问,链表中应该只能存入一个元素,那么HashMap是如何将key-value存入链表的 某个节点的呢?实际上,HashMap会将每组键值对封装为一个Entry的实例,而后将该实例存入链表。
hashcode方法
HashMap的存取是依赖于key的hashcode方法的返回值的,而hashcode方法其实是在Object中定义的。其定义以下:
int hashCode() 重写一个类的hashcode()方法有如下注意事项: 一、若一个类重写了equals方法,那么就应当重写hashcode()方法。 二、若两个对象的equals方法比较为true,那么它们应当具备相同的hashcode值。 三、对于同一个对象而言,在内容没有发生改变的状况下,屡次调用hashCode()方法应当老是返回相同的值。 四、对于两个对象equals比较为false的,并不要求其hashcode值必定不一样,可是应尽可能保证不一样,这样能够提升散列表性能。
装载因子及HashMap优化
在散列表中有一下名词须要了解: Capacity:容量, hash表里bucket(桶)的数量, 也就是散列数组大小. Initial capacity:初始容量, 建立hash表的时 初始bucket的数量, 默认构建容量是16. 也可使用特定容量. Size : 大小, 当前散列表中存储数据的数量. Load factor:加载因子, 默认值0.75(就是75%), 向散列表增长数据时若是 size/capacity 的值大于Load factor则发生扩容而且从新散列(rehash). 那么当加载因子较小时候散列查找性能会提升, 同时也浪费了散列桶空间容量. 0.75是性能和空间相对平 衡结果. 在建立散列表时候指定合理容量, 从而能够减小rehash提升性能。
有序Map
LinkedHashMap实现有序的Map
Map 接口的哈希表和链表实现,具备可预知的迭代顺序。此实现与 HashMap 的不一样之处在于, LinkedHashMap维护着一个双向循环链表。此链表定义了迭代顺序,该迭代顺序一般就是将存放元素的顺序。
须要注意的是,若是在Map中从新存入以有的key,那么key的位置会不会发生改变,只是将value值替换。
示例代码
Map中的API
示例代码:
//Map能够分别制定Key与Value的类型 Map<String,Integer> map = new HashMap<String,Integer>(); /* * V put(K k,V v) * 将指定的key与value存入Map中 * 因为Map要求key不容许重复,因此 * 若使用已有的key存入一个value,则会 * 将该key原有对应的value值替换,并将 * 被替换的value返回。若使用新的key, * 则返回值为NULL。 */ map.put("语文", 98); map.put("数学", 97); map.put("英语", 96); map.put("物理", 95); map.put("化学", 98); System.out.println(map); Integer num = map.put("语文", 99); System.out.println(map); System.out.println(num); /* * V get(K k) * 根据给定的key获取对应的value * 若给定的key在Map中不存在,则 * 返回值为null */ num = map.get("政治"); System.out.println(num); num = map.get("英语"); System.out.println(num); /* * V remove(K k) * 根据给定的key从Map中删除对应的 * 这组键值对。而返回值则是该key对应 * 的value */ System.out.println("删除英语..."); Integer old = map.remove("英语"); System.out.println(map); System.out.println(old);
示例代码:
//Map能够分别制定Key与Value的类型 Map<String,Integer> map = new HashMap<String,Integer>(); map.put("语文", 98); map.put("数学", 97); map.put("英语", 96); map.put("物理", 95); map.put("化学", 98); System.out.println(map); boolean containsKey = map.containsKey("语文"); System.out.println(containsKey);
遍历Map
示例代码:
/** * Map的遍历 * 遍历Map有三种方式: * 1:遍历全部的key * 2:遍历全部的key-value对 * 3:遍历全部的value(相对而言不经常使用) * @author Administrator * */ public class MapDemo3 { public static void main(String[] args) { Map<String,Integer> map = new LinkedHashMap<String,Integer>(); map.put("语文", 98); map.put("数学", 97); map.put("英语", 96); map.put("物理", 95); map.put("化学", 98); /* * 遍历全部的key * * Set<K> keySet() * 将当前Map中全部的key存入到一个Set * 集合中,并将该集合返回。 */ Set<String> keySet = map.keySet(); for(String key : keySet){ System.out.println("key:"+key); } /* * 遍历每一组键值对 * Entry是Map的内部类,其每个实例用于 * 表示一组键值对。有key,value两个主要 * 属性组成。 * * Set<Entry<K,V>> entrySet() * */ Set<Entry<String,Integer>> entrySet = map.entrySet(); for(Entry<String,Integer> entry:entrySet){ String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key+":"+value); } /* * Collection<V> values() * 将当前Map中全部的value存入到一个集合中 * 而后返回该集合 */ Collection<Integer> values = map.values(); for(Integer value : values){ System.out.println("value:"+value); } } }
建立File对象
java.io.File用于表示文件(目录),也就是说程序员能够经过File类在程序中操做硬盘上的文件和目录。 File类只用于表示文件(目录)的信息(名称、大小等),换句话说只能访问文件或目录的相关属性,不能对文件的内容进行访问。
File(pathname)
File提供了较多的构造方法来建立实例,其中之一就是:
File(String pathname)
经过将给定路径名字符串转换成抽象路径名来建立一个新 File 实例
提示:抽象路径应尽可能使用相对路径,而且目录的层级分隔符不要直接写”/”或”\”,应使用File.separator这个常量表示,以免不一样系统带来的差别。
File(parent,child)
File的另外一个经常使用构造方法:
File(File parent,String child)
根据 parent 抽象路径名和 child 路径名字符串建立一个新 File 实例。
isFile() 方法
File的isFile方法用于判断当前File对象表示的是否为一个文件
boolean isFile()
该方法若返回true,这表示File表示的是一个文件。
File表示文件信息
length方法
File的length方法用于返回由此抽象路径名表示的文件的长度,其定义为:
long length()
该方法返回的long值表示该文件所占用的字节量。
exists方法
File的exists方法用于测试此抽象路径名表示的文件或目录是否存在,其方法定义:
boolean exists()
若该File表示的文件或目录存在则返回true,不然返回false。
createNewFile方法
File的createNewFile方法用于当且仅当不存在具备此抽象路径名指定的名称的文件时,原子地建立由此抽象路径名指定的一个新的空文件。 其方法定义:
boolean createNewFile()
返回值:若是指定的文件不存在并成功地建立,则返回 true;若是指定的文件已经存在,则返回 false 。
delete方法
File的delete方法用于删除此抽象路径名表示的文件或目录。 其方法定义:
boolean delete()
返回值:当且仅当成功删除文件或目录时,返回 true;不然返回 false。
须要注意的是,若此File对象所表示的是一个目录时,在删除时须要保证此为空目录才能够成功删除(目录中不能含有任何子项)。
isDirectory()
File的isDirectory方法用于判断当前File对象表示的是否为一个目录
boolean isDirectory()
返回值:若File对象表示的是一个目录,则返回true
File表示目录信息
mkdir方法
File的mkdir方法用于建立此抽象路径名指定的目录。其方法定义:
boolean mkdir()
返回值:当且仅当已建立目录时,返回 true;不然返回 false
mkdirs方法
File的mkdirs方法用于建立此抽象路径名指定的目录,包括全部必需但不存在的父目录。注意,此操做失败时也可能已经成功地建立了一部分必需的父目录。其方法定义:
boolean mkdirs()
返回值:当且仅当已建立目录以及全部必需的父目录时,返回 true;不然返回 false
delete方法
前面咱们介绍了File的delete方法是用于删除此抽象路径名表示的文件或目录。在此强调,在删除目录时要特别注意:须要保证此为空目录才能够成功删除(目录中不能含有任何子项)。
listFiles方法
File的listFiles方法用于返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。其方法定义:
File[] listFiles()
返回值:抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件和目录。若是目录为空,那么数组也将为空。若是抽象路径名不表示一个目录,或者发生 I/O 错误,则返回 null。
FileFilter接口
经过listFiles方法咱们能够获取一个目录下的全部子项,但有些时候咱们并不但愿获取所有子项,而是想获取部分知足咱们实际需求的子项时,咱们可使用File的重载方法:
File[] listFiles(FileFilter filter)
这里咱们看到,该重载方法 要求咱们传入一个参数,其类型是FileFilter。什么是FileFilter呢? FileFilter是用于抽象路径名的过滤器,说白了就是定义一个规律规则,那么结合listFiles方法,咱们就能够将知足此过滤规则的子项返回,其余则忽略。
FileFilter是一个接口,因此当咱们须要定义某种过滤规则时,咱们能够定义一个类来实现这个接口,而此接口的实例可传递给 File 类的 listFiles(FileFilter) 方法。
例如:
File[] list = dir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().startsWith("."); } });
该方法的参数FileFilter实例的accept方法并进行过滤,listFiles方法会将全部accept方法返回true的子项保留并返回。这个例子里咱们会将dir中子项的名字以"."开头的返回。
示例代码
访问File
使用File能够:
1:访问文件或目录的属性信息
2:操做文件或目录(建立,删除)
3:查看目录中的子项
示例代码:
File file = new File( "."+File.separator+"test.txt" ); //获取文件名 String name = file.getName(); System.out.println("name:"+name); //文件大小(字节) long length = file.length(); System.out.println("length:"+length+"字节"); //最后修改时间 long lm = file.lastModified(); boolean cr = file.canRead(); boolean cw = file.canWrite(); boolean ih = file.isHidden();
使用File建立文件
/* * 在当前项目根目录下建立文件demo.txt */ File file = new File("demo.txt"); /* * boolean exists() * 判断当前File表示的文件或目录是否真实 * 存在。 */ if(!file.exists()){ System.out.println("不存在!"); file.createNewFile(); System.out.println("建立完毕!"); }
使用File删除现有文件
/* * 删除项目根目录下的demo.txt */ File file = new File("demo.txt"); if(file.exists()){ //删除File表示的文件 file.delete(); System.out.println("删除完毕!"); }
使用File建立一个目录
/* * 在当前项目根目录下建立一个叫demo的目录 */ File dir = new File("demo"); if(!dir.exists()){ //建立目录 dir.mkdir(); System.out.println("建立完毕!"); }
使用File建立一个多级目录
/* * 在当前目录下建立目录:a/b/c/d/e/f */ File dir = new File( "a"+File.separator+ "b"+File.separator+ "c"+File.separator+ "d"+File.separator+ "e"+File.separator+ "f"); if(!dir.exists()){ /* * mkdirs会在建立当前目录的同时将 * 全部不存在的父目录所有自动建立 */ dir.mkdirs(); System.out.println("建立完毕!"); }
删除一个目录
File dir = new File("demo"); if(dir.exists()){ /* * 若当前目录中含有子项,该目录不能 * 被删除。 */ dir.delete(); System.out.println("删除完毕!"); }
使用File获取其表示的目录中的全部子项,一个目录中的子项无非仍是文件或目录
/* * 获取当前目录下的全部子项 */ File dir = new File("."); /* * File[] listFiles() * 将当前目录中全部子项(若干File对象表示) * 存入一个数组后返回 */ File[] subs = dir.listFiles(); for(File sub : subs){ /* * boolena isFile() * 判断当前File对象表示的是否为一个文件 */ if(sub.isFile()){ System.out.print("文件:"); } if(sub.isDirectory()){ System.out.print("目录:"); } System.out.println(sub.getName()); }
File的listFiles方法有一个重载,容许咱们指定一个文件过滤器,而后将File表示的目录下,知足过滤器要求的子项获取回来。
File dir = new File("."); /* * 获取当前目录下全部名字以"."开头的子项 * * FileFilter是一个接口,有一个抽象方法: * accept,该方法的做用是定义过滤条件 */ FileFilter filter = new FileFilter(){ public boolean accept(File file) { String name = file.getName(); System.out.println("正在过滤:"+name); return name.startsWith("."); } }; File[] subs = dir.listFiles(filter); for(File sub : subs){ System.out.println(sub.getName()); }
将指定的File表示的文件或目录删除
public static void main(String[] args) { File dir = new File("a"); delete(dir); } /** * 删除指定File表示的文件或目录 * @param file * 递归 */ public static void delete(File file){ if(file.isDirectory()){ //将当前目录下的全部子项先删除 for(File sub : file.listFiles()){ delete(sub); } } file.delete(); }
建立对象
简介
Java提供了一个能够对文件随机访问的操做,访问包括读和写操做。该类名为RandomAccessFile。该类的读写是基于指针的操做。
只读模式
RandomAccessFile在对文件进行随机访问操做时有两个模式,分别为只读模式(只读取文件数据),和读写模式(对文件数据进行读写)。
只读模式:
在建立RandomAccessFile时,其提供的构造方法要求咱们传入访问模式:
RandomAccessFile(File file,String mode) RandomAccessFile(String filename,String mode)
其中构造方法的第一个参数是须要访问的文件,而第二个参数则是访问模式:
r”:字符串”r”表示对该文件的访问是只读的。
读写模式
建立一个基于文件访问的读写模式的RandomAccessFile咱们只须要在第二个参数中传入”rw”便可。
RandomAccessFile raf = new RandomAccessFile(file,”rw”);
那么这时在使用RandomAccessFile对该文件的访问就是又可读又可写的。
字节数据读写操做
write(int d)方法
RandomAccessFile提供了一个能够向文件中写出字节的方法:
void write(int d)
该方法会根据当前指针所在位置处写入一个字节,是将参数int的”低8位”写出。
read()方法
RandomAccessFile提供了一个能够从文件中读取字节的方法:
int read()
该方法会从RandomAccessFile当前指针位置读取一个byte(8位) 填充到int的低八位, 高24位为0, 返回值范围正数: 0~255, 若是返回-1表示读取到了文件末尾EOF(EOF:End Of File)! 每次读取后自动移动文件指针, 准备下次读取。
write(byte[] d)方法
RandomAccessFile提供了一个能够向文件中写出一组字节的方法:
void write(byte[] d)
该方法会根据当前指针所在位置处连续写出给定数组中的全部字节,与该方法类似的还有一个经常使用方法:
void write(byte[] d,int offset,int len)
该方法会根据当前指针所在位置处连续写出给定数组中的部分字节,这个部分是从数组的offset处开始,连续len个字节。
read(byte[] d)方法
RandomAccessFile提供了一个能够从文件中批量读取字节的方法:
int read(byte[] b)
该方法会从文件中尝试最多读取给定数组的总长度的字节量,并从给定的字节数组第一个位置开始,将读取到的字节顺序存放至数组中,返回值为实际读取到的字节量 。
close方法
RandomAccessFile在对文件访问的操做所有结束后,要调用close()方法来释放与其关联的全部系统资源。
void close()
例如:
RandomAccessFile raf = new RandomAccessFile(file,”rw”); …..//读写操做 raf.close();//访问完毕后要关闭以释放系统资源。
文件指针操做
getFilePointer方法
RandomAccessFile的读写操做都是基于指针的,也就是说老是在指针当前所指向的位置进行读写操做。
RandomAccessFile提供了一个能够获取当前指针位置的方法:
long getFilePointer()
RandomAccessFile在建立时默认指向文件开始(第一个字节),经过getFilePointer方法获取指针位置时值是"0"。
例如:
RandomAccessFile raf = new RandomAccessFile(file,”rw”); System.out.println(raf.getFilePointer());//0 raf.write(‘A’);//写出一个字节后,指针自动向后移动到下一个字节位置 System.out.println(raf.getFilePointer());//1 raf.writeInt(3); System.out.println(raf.getFilePointer());//5 raf.close();
seek方法
RandomAccessFile的提供了一个方法用于移动指针位置。
void seek(long pos)
使用该方法能够移动指针到指定位置。
例如:
RandomAccessFile raf = new RandomAccessFile(file,”rw”); System.out.println(raf.getFilePointer());//0 raf.write(‘A’);//指针位置1 raf.writeInt(3);//指针位置5 raf.seek(0);//将指针移动到文件开始处(第一个字节的位置) System.out.println(raf.getFilePointer());//0 raf.close();
skipBytes方法
RandomAccessFile的提供了一个方法能够尝试跳过输入的 n 个字节以丢弃跳过的字节,方法定义为:
int skipBytes(int n)
该方法可能跳过一些较少数量的字节(可能包括零)。这可能由任意数量的条件引发;在跳过n个字节以前已到达文件的末尾只是其中的一种可能。此方法不抛出 EOFException。返回跳过的实际字节数。若是 n 为负数,则不跳过任何字节。
示例代码
对项目根目录下的demo.dat文件进行读写操做
RandomAccessFile raf = new RandomAccessFile( "demo.dat","rw" ); //int i = 1; /* * void write(int i) * 向文件中写出一个字节,写出的是给定的 * int值对应的二进制中的"低八位" * vvvvvvvv * 00000000 00000000 00000000 00000001 * * 00000000 00000000 00000001 00000001 */ raf.write(257); // 文件中只有1 /* * 关闭释放资源 */ raf.close();
读取字节
RandomAccessFile raf = new RandomAccessFile( "demo.dat","r" ); /* * byte read() * 该方法会读取一个字节,并将该字节转换为 * 一个int值保存。该int值只有"低八位"有效 * 若该int值表示的数字为-1,则表示读取到了 * 文件末尾。 * 00000000 00000000 00000000 11111111 */ int d = raf.read(); System.out.println(d); raf.close();
RandomAccessFile读写基本类型数据以及基于指针的读写操做原理
RandomAccessFile raf = new RandomAccessFile( "test.dat","rw" ); long pos = raf.getFilePointer(); System.out.println("pos:"+pos); //向文件中写入int最大值 int max = Integer.MAX_VALUE; /* * vvvvvvvv * 01111111 11111111 11111111 11111111 * */ raf.write(max>>>24); raf.write(max>>>16); raf.write(max>>>8); raf.write(max); System.out.println("pos:"+raf.getFilePointer()); raf.writeInt(max); System.out.println("pos:"+raf.getFilePointer()); raf.writeLong(123L); System.out.println("pos:"+raf.getFilePointer()); raf.writeDouble(123.123); System.out.println("pos:"+raf.getFilePointer()); /* * void seek(long pos) * 移动指针到指定的位置 */ raf.seek(0); System.out.println("seek到0"); System.out.println("pos:"+raf.getFilePointer()); //EOF (end of file) int i = raf.readInt(); System.out.println(i); System.out.println("pos:"+raf.getFilePointer()); //读double raf.seek(16); double d = raf.readDouble(); System.out.println(d); System.out.println("pos:"+raf.getFilePointer()); raf.close();
使用RAF复制文件
/* * 1:建立一个RAF读取原文件 * 2:再建立一个RAF用于向目标文件中写 * 3:循环从原文件中读取每个字节,而后 * 将该字节的内容写入到目标文件中,直到 * 读取到原文件的末尾 */ //1 RandomAccessFile src = new RandomAccessFile( "music.mp3","r" ); //2 RandomAccessFile desc = new RandomAccessFile( "music_copy.mp3","rw" ); long start = System.currentTimeMillis(); //3 int d = -1; while((d=src.read())!=-1){ desc.write(d); } long end = System.currentTimeMillis(); System.out.println("复制完毕!耗时:"+(end-start)+"ms"); src.close(); desc.close();
基于缓存的文件复制
RandomAccessFile src = new RandomAccessFile( "music.mp3","r" ); RandomAccessFile desc = new RandomAccessFile( "music_copy2.mp3","rw" ); /* * int read(byte[] d) * 一次性读取给定的字节数组长度的字节量 * 并存入给定的数组中,而返回值为实际读取 * 到得字节量 * 若读取到了文件末尾则返回-1 */ int len = -1; byte[] buf = new byte[1024*10]; long start = System.currentTimeMillis(); while((len = src.read(buf))!=-1){ /* * void write(byte[] d) * 一次性将给定的字节数组中的全部字节 * 写出去 * void write(byte[] d,int offset,int len) * 将给定数组中offset指定的位置处开始的连续 * len个字节写出 * */ desc.write(buf,0,len); } long end = System.currentTimeMillis(); System.out.println("复制完毕!耗时:"+(end-start)+"毫秒"); src.close(); desc.close();
InputStream与OutputStream
输入与输出
咱们编写的程序除了自身会定义一些数据信息外,常常还会引用外界的数据,或是将自身的数据发送到外界。好比,咱们编写的程序想读取一个文本文件,又或者咱们想将程序中的某些数据写入到一个文件中。这时咱们就要使用输入与输出。
什么是输入:输入是一个从外界进入到程序的方向,一般咱们须要“读取”外界的数据时,使用输入。因此输入是用来读取数据的。
什么是输出:输出是一个从程序发送到外界的方向,一般咱们须要”写出”数据到外界时,使用输出。因此输出是用来写出数据的。
节点流与处理流
按照流是否直接与特定的地方 (如磁盘、内存、设备等) 相连,分为节点流和处理流两类。
处理流的构造方法老是要带一个其余的流对象作参数。一个流对象通过其余流的屡次包装,称为流的连接。
InputStream与OutputStream经常使用方法
InputStream是全部字节输入流的父类,其定义了基础的读取方法,经常使用的方法以下:
int read()
读取一个字节,以int形式返回,该int值的”低八位”有效,若返回值为-1则表示EOF。
int read(byte[] d)
尝试最多读取给定数组的length个字节并存入该数组,返回值为实际读取到的字节量。
OutputStream是全部字节输出流的父类,其定义了基础的写出方法,经常使用的方法以下:
void write(int d)
写出一个字节,写的是给定的int的”低八位”
void write(byte[] d)
将给定的字节数组中的全部字节所有写出
文件流
建立FOS对象(重写模式)
FileOutputStream是文件的字节输出流,咱们使用该流能够以字节为单位将数据写入文件。
构造方法:
FileOutputStream(File file)
建立一个向指定 File 对象表示的文件中写入数据的文件输出流。
例如:
FIle file = new File("demo.dat"); FileOutputStream fos = new FileOutputStream(file);
构造方法:
FileOutputStream(String filename):
建立一个向具备指定名称的文件中写入数据的输出文 件流。
例如:
FileOutputStream fos = new FileOutputStream("demo.dat");
这里须要注意,若指定的文件已经包含内容,那么当使用FOS对其写入数据时,会将该文件中原有数据所有清除。
建立FOS对象(追加模式)
经过上一节的构造方法建立的FOS对文件进行写操做时会覆盖文件中原有数据。若想在文件的原有数据以后追加新数据则须要如下构造方法建立FOS
构造方法:
FileOutputStream(File file,boolean append)
建立一个向指定 File 对象表示的文件中写入数据的文件输出流。
例如:
File file = new File("demo.dat"); FileOutputStream fos = new FileOutputStream(file,true);
构造方法:
FileOutputStream(String filename,boolean append):
建立一个向具备指定名称的文件中写入数据的输出文 件流。
例如:
FileOutputStream fos = new FileOutputStream("demo.dat",true);
以上两个构造方法中,第二个参数若为true,那么经过该FOS写出的数据都是在文件末尾追加的。
建立FIS对象
FileInputStream是文件的字节输入流,咱们使用该流能够以字节为单位读取文件内容。
FileInputStream有两个经常使用的构造方法:
FileInputStream(File file):
建立用于读取给定的File对象所表示的文件FIS
例如:
File file = new File("demo.dat"); FileInputStream fis = new FileInputStream(file);//建立一个用于读取demo.dat文件的输入流
另外一个构造方法:
FileInputStream(String name):
建立用于读取给定的文件系统中的路径名name所指定的文件的FIS
例如
FileInputStream fis //建立一个用于读取demo.dat文件的输入流 = new FileInputStream("demo");
read()和write(int d)方法
FileInputStream继承自InputStream,其提供了以字节为单位读取文件数据的方法read。
int read()
今后输入流中读取一个数据字节,若返回-1则表示EOF(End Of File)
FileOutputStream继承自OutputStream,其提供了以字节为单位向文件写数据的方法write。
void write(int d)
将指定字节写入此文件输出流。,这里只写给定的int值的”低八位”
例如
FileOutputStream fos = new FileOutputStream("demo.dat"); fos.write('A');//这里要注意,char占用2个字节,但这里只写入了1个字节。
read(byte[] d)和write(byte[] d)方法
FileInputStream也支持批量读取字节数据的方法:
int read(byte[] b)
今后输入流中将最多 b.length 个字节的数据读入一个 byte 数组中 。
FileOutputStream也支持批量写出字节数据的方法:
void write(byte[] d)
将 b.length 个字节从指定 byte 数组写入此文件输出流中。
例如:
FileOutputStream fos = new FileOutputStream("demo.txt"); byte[] data = "HelloWorld".getBytes(); fos.write(data);//会将HelloWorld的全部字节写入文件。
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流的方法:
void write(byte[] d,int offset,int len)
例如:
FileOutputStream fos = new FileOutputStream("demo.txt"); byte[] data = "HelloWorld".getBytes(); fos.write(data,5,5);//只会将world这5个字节写入文件。
缓冲流
BOS基本工做原理
与缓冲输入流类似,在向硬件设备作写出操做时,增大写出次数无疑也会下降写出效率,为此咱们可使用缓冲输出流来一次性批量写出若干数据减小写出次数来提升写 出效率。
BufferedOutputStream缓冲输出流内部也维护着一个缓冲区,每当咱们向该流写数据时,都会先将数据存入缓冲区,当缓冲区已满时,缓冲流会将数据一次性所有写出。
BOS实现写出缓冲
实现写出缓冲:
public void testBos()throws Exception { FileOutputStream fos = new FileOutputStream("demo.dat"); //建立缓冲字节输出流 BufferedOutputStream bos = new BufferedOutputStream(fos); //全部字节被存入缓冲区,等待一次性写出 bos.write("helloworld".getBytes()); //关闭流以前,缓冲输出流会将缓冲区内容一次性写出 bos.close(); }
BOS的flush方法
使用缓冲输出流能够提升写出效率,可是这也存在着一个问题,就是写出数据缺少即时性。有时咱们须要在执行完某些写出操做后,就但愿将这些数据确实写出,而非在缓冲区中保存直到缓冲区满后才写出。这时咱们可使用缓冲流的一个方法flush。
void flush()
清空缓冲区,将缓冲区中的数据强制写出。
例如:
BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("demo.dat") ); bos.write('a');//并无向磁盘写出,而是写入到了BOS的缓存中 bos.flush();//强制将缓存中的数据一次性写出,这时‘a’才会被写入磁盘 bos.close();//实际上,close()方法在变比缓冲流前也调用了flush()
BIS基本工做原理
在读取数据时若以字节为单位读取数据,会致使读取次数过于频繁从而大大的下降读取效率。为此咱们能够经过提升一次读取的字节数量减小读写次数来提升读取的效率。
BufferedInputStream是缓冲字节输入流。其内部维护着一个缓冲区(字节数组),使用该流在读取一个字节时,该流会尽量多的一次性读取若干字节并存入缓冲区,而后逐一的将字节返回,直到缓冲区中的数据被所有读取完毕,会再次读取若干字节从而反复。这样就减小了读取的次数,从而提升了读取效率。
BIS是一个处理流,该流为咱们提供了缓冲功能。
BIS实现输入缓冲
使用缓冲流来实现文件复制:
FileInputStream fis = new FileInputStream("java.zip"); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream("copy_java.zip"); BufferedOutputStream bos = new BufferedOutputStream(fos); int d = -1; while((d = bis.read())!=-1){ bos.write(d); } bis.close();//读写完毕后要关闭流,只须要关闭最外层的流便可 bos.close();
对象流
对象序列化概念
对象是存在于内存中的。有时候咱们须要将对象保存到硬盘上,又有时咱们须要将对象传输到另外一台计算机上等等这样的操做。这时咱们须要将对象转换为一个字节序列,而这个过程就称为对象序列化。相反,咱们有这样一个字节序列须要将其转换为对应的对象,这个过程就称为对象的反序列化。
使用OOS实现对象序列化
ObjectOutputStream是用来对对象进行序列化的输出流。
其实现对象序列化的方法为:
void writeObject(Object o)
该方法能够将给定的对象转换为一个字节序列后写出。
例如:
Emp emp = new Emp("张三",12,"男"); FileOutputStream fos = new FileOutputStream("Emp.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(emp);//将emp对象序列化后写入文件 oos.close();
使用OIS实现对象反序列化
ObjectInputStream是用来对对象进行反序列化的输入流。
其实现对象反序列化的方法为:
Object readObject()
该方法能够从流中读取字节并转换为对应的对象。
例如:
FileInputStream fis = new FileInputStream("Emp.obj"); ObjectInputStream ois = new ObjectInputStream(fis); Emp emp = (Emp)ois.readObject();//将Emp对象从文件中读取并反序列 .... ois.close();
Serializable接口
ObjectOutputStream在对对象进行序列化时有一个要求,就是须要序列化的对象所属的类必须实现Serializable接口。
实现该接口不须要重写任何方法。其只是做为可序列化的标志。
一般实现该接口的类须要提供一个常量serialVersionUID,代表该类的版本。若不显示的声明,在对象序列化时也会根据当前类的各个方面计算该类的默认serialVersionUID,但不一样平台编译器实现有所不一样,因此若向跨平台,都应显示的声明版本号。
若是声明的类序列化存到硬盘上面,以后随着需求的变化更改了类别的属性(增长或减小或更名),那么当反序列化时,就会出现InvalidClassException,这样就会形成不兼容性的问题。 但当serialVersionUID相同时,它就会将不同的field以type的预设值反序列化,可避开不兼容性问题。
例如:
public class Emp implements Serializable{ private static final long serialVersionUID = 1L; private String name; private int age; private String gender; //getter and setter and other ... }
transient关键字
对象在序列化后获得的字节序列每每比较大,有时咱们在对一个对象进行序列化时能够忽略某些没必要要的属性,从而对序列化后获得的字节序列”瘦身”。
关键字 transient
被该关键字修饰的属性在序列化时其值将被忽略
例如:
public class Emp implements Serializable{ private static final long serialVersionUID = 1L; private String name; private transient int age;//该属性在序列化时会被忽略 private String gender; //getter and setter and other ... }
Reader和Writer
字符流原理
Reader是全部字符输入流的父类,而Writer是全部字符输出流的父类。字符流是以字符(char)为单位读写数据的。一次处理一个unicode。字符流都是高级流,其底层都是依靠字节流进行读写数据的,因此底层仍然是基于字节读写数据的。
经常使用方法
Reader的经常使用方法:
int read()
读取一个字符,返回的int”值低16”位有效。
int read(char[] chs)
从该流中读取一个字符数组length个字符并存入该数组,返回值为实际读取到的字符量。
Writer的经常使用方法:
void write(int c)
写出一个字符,写出给定int值”低16”位表示的字符。
void write(char[] chs)
将给定字符数组中全部字符写出。
void write(String str)
将给定的字符串写出
void write(char[] chs,int offset,int len):
将给定的字符数组中从offset处开始连续的len个字符写出
转换流
字符转换流原理
InputStreamReader:字符输入流, 使用该流能够设置字符集,并按照指定的字符集从流中按照该编码将字节数据转换为字符并读取。
OutputStreamWriter:字符输出流,使用该流能够设置字符集,并按照指定的字符集将字符转换为对应字节后经过该流写出。
指定字符编码
InputStreamReader的构造方法容许咱们设置字符集:
InputStreamReader(InputStream in,String charsetName)
基于给定的字节输入流以及字符编码建立ISR
InputStreamReader(InputStream in)
该构造方法会根据系统默认字符集建立ISR
OutputStreamWriter:的构造方法:
OutputStreamWriter(OutputStream out,String charsetName)
基于给定的字节输出流以及字符编码建立OSW
OutputStreamWriter(OutputStream out)
该构造方法会根据系统默认字符集建立OSW
使用OutputStreamWriter
... public void testOutput() throws IOException{ FileOutputStream fos = new FileOutputStream("demo.txt"); OutputStreamWriter writer //这里使用的字符编码为UTF-8 = new OutputStreamWriter(fos,"UTF-8"); String str = "你们好!";//UTF-8中文为3个字节,英文符号占1个字节 writer.write(str);//写出后该文件大小应该为10字节 writer.close(); } ...
使用InputStreamReader
... public void testInput() throws IOException{ FileInputStream fis = new FileInputStream("demo.txt"); /* * 这里设置了字符编码为GBK * 以后再经过ISR读取demo.txt文件时 * 就使用GBK编码读取字符了 */ InputStreamReader reader = new InputStreamReader(fis,"GBK"); int c = -1; while((c = reader.read()) != -1){ System.out.print((char)c); } reader.close(); } ...
PrintWriter
建立PrintWriter对象
PrintWriter是具备自动行刷新的缓冲该字符输出流。其提供了比较丰富的构造方法:
PrintWriter(File file) PrintWriter(String fileName) PrintWriter(OutputStream out) PrintWriter(OutputStream out,boolean autoFlush) PrintWriter(Writer writer) PrintWriter(Writer writer,boolean autoFlush)
其中参数为OutputStream与Writer的构造方法提供了一个能够传入boolean值参数,该参数用于表示PrintWriter是否具备自动行刷新。
PrintWriter的重载print和println方法
使用PrintWriter写出字符串时咱们一般不使用Writer提供的write()相关方法,而是使用print和println等方法,PrintWriter提供了若干重载的print与println方法,其中println方法是在写出数据后自动追加一个系统支持的换行符。
重载方法有:
void print(int i)//打印整数 void print(char c)//打印字符 void print(boolean b)//打印boolean值 void print(char[] c)//打印字符数组 void print(double d)//打印double值 void print(float f)//打印float值 void print(long l)//打印long值 void print(String str)//打印字符串
注:这些方法还有相似的println方法,参数与上面相同。
使用PW输出字符数据
FileOutputStream fos = new FileOutputStream("demo.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8"); //建立带有自动行刷新的PW PrintWriter pw = new PrintWriter(osw,true); pw.println("你们好!"); pw.close();
BufferedReader
构建BufferedReader对象
BufferedReader是缓冲字符输入流,其内部提供了缓冲区,能够提升读取效率。
BufferedReader的经常使用构造方法:
BufferedReader(Reader reader)
例如:
FileInputStream fis = new FileInputStream("demo.txt"); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); ….
注:由于BufferedReader在构造实例时须要传入一个字符流,因此当咱们想基于一个字节流进行读取时,要先将字节流转换为字符流后方可建立缓冲字符输入流BufferedReader。
使用BR读取字符串
BufferedReader提供了一个能够便于读取一行字符串:
String readLine()
该方法连续读取一行字符串,直到读取到换行符为止,返回的字符串中不包含该换行符。若EOF则该方法返回null。
例如:
FileInputStream fis = new FileInputStream("demo.txt"); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); String str = null; while((str = br.readLine()) != null){ System.out.println(str); } br.close();
异常处理概述
使用返回值状态标识异常
在JAVA语言出现之前,传统的异常处理方式多采用返回值来标识程序出现的异常状况,这种方式虽然为程序员所熟悉,但却有多个坏处。
首先,一个API能够返回任意的返回值,而这些返回值自己并不能解释该返回值是否表明一个异常状况发生了和该异常的具体状况,须要调用API的程序本身判断并解释返回值的含义。
其次,并无一种机制来保证异常状况必定会获得处理,调用程序能够简单的忽略该返回值,须要调用API的程序员记住去检测返回值并处理异常状况。这种方式还让程序代码变得冗长,尤为是当进行IO操做等容易出现异常状况的处理时,代码的很大部分用于处理异常状况的switch分支,程序代码的可读性变得不好。
异常处理机制
当程序中抛出一个异常后,程序从程序中致使异常的代码处跳出,java虚拟机检测寻找和try关键字匹配的处理该异常的catch块,若是找到,将控制权交到catch块中的代码,而后继续往下执行程序,try块中发生异常的代码不会被从新执行。若是没有找处处理该异常的catch块,在全部的finally块代码被执行和当前线程的所属的ThreadGroup的uncaughtException方法被调用后,遇到异常的当前线程被停止。
异常的捕获和处理
Throwable,Error和Exception
Java异常结构中定义有Throwable类,Exceotion和Error是其派生的两个子类。其中Exception表示因为网络故障、文件损坏、设备错误、用户输入非法等状况致使的异常,这类异常是能够经过Java异常捕获机制处理的。而Error表示Java运行时环境出现的错误,例如:JVM内存溢出等。
try-catch
try {...} 语句指定了一段代码,该段代码就是一次捕获并处理例外的范围。在执行过程当中,该段代码可能会产生并抛出一种或几种类型的异常对象,它后面的catch语句分别对这些异常作相应的处理。
若是没有列外产生,全部的catch代码段都被略过不执行
在catch语句块中是对异常进行处理的代码。catch中声明的异常对( catch(SomeException e) )封装了异常事件发生的信息,在catch语句块中可使用这个对象的一些方法获取这些信息
常见格式:
... try{ //可能出现异常的代码片断 }catch(Exception e){ //处理该异常的代码片断 } ...
多个catch
每一个try语句块能够伴随一个或多个catch语句,用于处理可能产生的不一样类型的异常 。catch捕获的异常类型由上至下的捕获异常类型的顺序应是子类到父类的
例如
try{ … }catch(NullPointerException e){ //子类异常应在上面捕获 … }catch(RuntimeException e){ //父类异常在下面捕获 … }catch(Exception e){ //应养成最终捕获Exception的习惯 … }
一般在书写代码的时候,咱们应当在最后一个catch中捕获Exception,这样能够保证代码不会由于出现一个未在catch中声明的异常而致使捕获失败使得程序终止。
finally的做用
finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序其它部分之前,可以对程序的状态做统一管理。
不管try所指定的程序块中是否抛出例外,finally所指定的代码都要被执行,一般在finally语句中能够进行资源的消除工做,如关闭打开的文件、删除临时文件等。
finally语句块只能定义在try语句块以后,或者最后一个catch语句块以后,且只能定义一次。
throw关键字
当程序发生错误而没法处理的时候,会抛出对应的异常对象,除此以外,在某些时刻,您可能会想要自行抛出异常,例如在异常处理结束后,再将异常抛出,让下一层异常处理区块来捕捉,若想要自行抛出异常,您可使用“throw”关键词,并生成指定的异常对象。
例如:
throw new ArithmeticException();
throws关键字
程序中会声明许多方法(Method),这些方法中可能会因某些错误而引起异常,但您不但愿直接在这个方法中处理这些异常,而但愿调用这个它的方法来统一处理,这时候您可使用“throws”关键词来声明这个方法将会抛出异常
例如:
public static void stringToDate(String str) throws ParseException{ …… }
重写方法时的throws
当使用继承时,在父类的某个方法上声明了throws抛出某些异常,而在子类中重写该方法时,咱们能够作如下的操做:
1. 不处理异常(重写方法时不声明throws) 2. 可仅在throws中声明父类中声明的部分异常 3. 可在throws中声明父类方法中抛出的异常的子类异常
可是不能作如下操做:
1. 重写方法时在throws中声明抛出额外的异常 2. 重写方法时在throws中声明父类方法中声明的抛出异常的父类异常
示例代码:
public class Father { public void dosome() throws IOException,AWTException{ } } public class Son extends Father{ //能够再也不声明抛出任何异常 // public void dosome(){ // // } //能够仅抛出父类抛出的部分异常 // public void dosome()throws IOException{ // // } /* * 能够抛出父类抛出异常的子类异常 */ // public void dosome() // throws FileNotFoundException{ // // } /* * 不能够抛出与父类抛出异常没有继承关系的其余异常 */ // public void dosome()throws SQLException{ // // } /* * 不能够抛出父类抛出异常的父类异常 */ // public void dosome()throws Exception{ // // } }
Java异常API
RuntimeException
Java异常能够分为可检测异常,非检测异常
RuntimeException 类属于非检测异常,由于普通JVM操做引发的运行时异常随时可能发生,此类异常通常是由特定操做引起。但这些操做在java应用程序中会频繁出现。所以它们不受编译器检查与处理或声明规则的限制 。
常见RuntimeException
IllegalArgumentException 抛出的异常代表向方法传递了一个不合法或不正确的参数 NullPointerException 当应用程序试图在须要对象的地方使用 null 时,抛出该异常 ArrayIndexOutOfBoundsException 当使用的数组下标超出数组容许范围时,抛出该异常 ClassCastException 当试图将对象强制转换为不是实例的子类时,抛出该异常 NumberFormatException 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
Exception经常使用API
printStackTrace
Throwable中定义了一个方法能够输出错误信息,用来跟踪异常事件发生时执行堆栈的内容。该方法定义为:
void printStackTrace()
例如:
try{ … }catch(Exception e){ e.printStackTrace();//输出执行堆栈信息 }
getMessage
Throwable中定义了一个方法能够获得有关异常事件的信息。该方法定义为:
String getMessage()
例如:
try{ … }catch(Exception e){ System.out.println(e.getMessage()); }
getCause
不少时候,当一个异常由另外一个异常致使异常而被抛出的时候,Java库和开放源代码会将一种异常包装成另外一种异常。这时,日志记录和打印根异常就变得很是重要。Java异常类提供了 getCause()方法来检索致使异常的缘由,这些能够对异常根层次的缘由提供更多的信息。该Java实践对代码的调试或故障排除有很大的帮助。另外,若是要把一个异常包装成另外一种异常,构造一个新异常就要传递源异常。
Throwable getCause()
获取该异常出现的缘由。
自定义Exception
自定义异常的意义
Java异常机制能够保证程序更安全和更健壮。虽然Java类库已经提供不少能够直接处理异常的类,可是有时候为了更加精准地捕获和处理异常以呈现更好的用户体验,须要开发者自定义异常。
继承Exception自定义异常
建立自定义异常类,语法格式:
public class [自定义异常类名] extends Exception{ … }
如何编写构造方法
当定义好自定义异常后,咱们能够经过Eclipse来自动生成相应的构造方法。
具体步骤以下:
例如:
public class MyException extends Exception{ public MyException() { super(); // TODO Auto-generated constructor stub } public MyException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public MyException(String message) { super(message); // TODO Auto-generated constructor stub } public MyException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } }
进程和线程
什么是进程
所谓进程(process)就是一块包含了某些资源的内存区域。操做系统利用进程把它的工做划分为一些功能单元。进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。线程只能归属于一个进程而且它只能访问该进程所拥有的资源。当操做系统建立一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。操做系统中有若干个线程在"同时"运行。一般,操做系统上运行的每个应用程序都运行在一个进程中,例如:QQ,IE等等。
注:进程并非真正意义上的同时运行,而是并发运行。后面咱们会具体说明。
什么是线程
一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一组系统资源,线程自己有一个供程序执行时的堆栈。线程在切换时负荷小,所以,线程也被称为轻负荷进程。一个进程中能够包含多个线程。
注:切换——线程并发时的一种现象,后面讲解并发时会具体说明。
进程与线程的区别
一个进程至少有一个线程。线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程当中拥有独立的内存单元,而多个线程共享内存,从而极大地提升了程序的运行效率。
线程在执行过程当中与进程的区别在于每一个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。可是线程不可以独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分能够同时执行。但操做系统并无将多个线程看作多个独立的应用来实现进程的调度和管理以及资源分配。
线程使用的场合
线程一般用于在一个程序中须要同时完成多个任务的状况。咱们能够将每一个任务定义为一个线程,使他们得以一同工做。
例如咱们在玩某个游戏时,这个游戏由操做系统运行,因此其运行在一个独立的进程中,而在游戏中咱们会听到某些背景音乐,某个角色在移动,出现某些绚丽的动画效果等,这些在游戏中都是同时发生的,但实际上,播放音乐是在一个线程中独立完成的,移动某个角色,播放某些特效也都是在独立的线程中完成的。这些事情咱们没法在单一线程中完成。
也能够用于在单一线程中能够完成,可是使用多线程能够更快的状况。好比下载文件。
好比迅雷,咱们尝尝会开到它会打开不少个节点来同时下载一个文件。
并发原理
经过上面几节知识咱们知道进程与线程都是并发运行的,那么什么是并发呢?
多个线程或进程”同时”运行只是咱们感官上的一种表现。事实上进程和线程是并发运行的,OS的线程调度机制将时间划分为不少时间片断(时间片),尽量均匀分配给正在运行的程序,获取CPU时间片的线程或进程得以被执行,其余则等待。而CPU则在这些进程或线程上来回切换运行。微观上全部进程和线程是走走停停的,宏观上都在运行,这种都运行的现象叫并发,可是不是绝对意义上的“同时发生。
注1:之因此这样作是由于CPU只有一个,同一时间只能作一件事情。但随着计算机的发展,出现了多核心CPU,例如两核心的CPU能够实现真正意义上的2线程同时运行,但由于CPU的时间片断分配给那个进程或线程是由线程调度决定,因此不必定两个线程是属于同一个进程的,不管如何咱们只须要知道线程或进程是并发运行便可。
注2:OS—Operating System咱们称为:操做系统
注3:线程调度机制是OS提供的一个用于并发处理的程序。java虚拟机自身也提供了线程调度机制,用于减轻OS切换线程带来的更多负担。
线程状态
对于程序而言,咱们实际上关心的是线程而非进程。经过上面学习的只是,咱们了解了什么是线程以及并发的相关知识。那么咱们来看看线程在其生命周期中的各个状态:
New:当咱们建立一个线程时,该线程并无归入线程调度,其处于一个new状态。 Runnable:当调用线程的start方法后,该线程归入线程调度的控制,其处于一个可运行状态,等待分配时间片断以并发运行。 Running:当该线程被分配到了时间片断后其被CPU运行,这是该线程处于running状态。 Blocked:当线程在运行过程当中可能会出现阻塞现象,好比等待用户输入信息等。但阻塞状态不是百分百出现的,具体要看代码中是否有相关需求。 Dead:当线程的任务所有运行完毕,或在运行过程当中抛出了一个未捕获的异常,那么线程结束,等待GC回收
建立线程
使用Thread建立线并启动线程
java.lang.Thread类是线程类,其每个实例表示一个能够并发运行的线程。咱们能够经过继承该类并重写run方法来定义一个具体的线程。其中重写run方法的目的是定义该线程要执行的逻辑。启动线程时调用线程的start()方法而非直接调用run()方法。start()方法会将当前线程归入线程调度,使当前线程能够开始并发运行。当线程获取时间片断后会自动开始执行run方法中的逻辑。
例如:
public class TestThread extends Thread{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println("我是线程"); } } }
建立和启动线程:
… Thread thread = new TestThread();//实例化线程 thread.start();//启动线程 …
当调用完start()方法后,run方法会很快执行起来。
使用Runnable建立并启动线程
实现Runnable接口并重写run方法来定义线程体,而后在建立线程的时候将Runnable的实例传入并启动线程。
这样作的好处在于能够将线程与线程要执行的任务分离开减小耦合,同时java是单继承的,定义一个类实现Runnable接口这样的作法能够更好的去实现其余父类或接口。由于接口是多继承关系。
例如:
public class TestRunnable implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println("我是线程"); } } }
启动线程的方法:
… Runnable runnable = new TestRunnable(); Thread thread = new Thread(runnable);//实例化线程并传入线程体 thread.start();//启动线程 …
使用内部类建立线程
一般咱们能够经过匿名内部类的方式建立线程,使用该方式能够简化编写代码的复杂度,当一个线程仅须要一个实例时咱们一般使用这种方式来建立。
例如:
继承Thread方式:
Thread thread = new Thread(){ //匿名类方式建立线程 public void run(){ //线程体 } }; thread.start();//启动线程
Runnable方式:
Runnable runnable = new Runnable(){ //匿名类方式建立线程 public void run(){ } }; Thread thread = new Thread(runnable); thread.start();//启动线程
线程操做API
Thread.currentThread方法
Thread的静态方法currentThread方法能够用于获取运行当前代码片断的线程。
Thread current = Thread.currentThread();
获取线程信息
Thread提供了 获取线程信息的相关方法:
long getId():返回该线程的标识符 String getName():返回该线程的名称 int getPriority():返回线程的优先级 Thread.state getState():获取线程的状态 boolean isAlive():测试线程是否处于活动状态 boolean isDaemon():测试线程是否为守护线程 boolean isInterrupted():测试线程是否已经中断
例如:
public class TestThread { public static void main(String[] args) { Thread current = Thread.currentThread(); long id = current.getId(); String name = current.getName(); int priority = current.getPriority(); Thread.State state = current.getState(); boolean isAlive = current.isAlive(); boolean isDaemon = current.isDaemon(); boolean isInterrupt = current.isInterrupted(); System.out.println("id:"+id); System.out.println("name:"+name); System.out.println("priority:"+priority); System.out.println("state:"+state); System.out.println("isAlive:"+isAlive); System.out.println("isDaemon:"+isDaemon); System.out.println("isInterrupt:"+isInterrupt); } }
线程优先级
线程的切换是由线程调度控制的,咱们没法经过代码来干涉,可是咱们能够经过提升线程的优先级来最大程度的改善线程获取时间片的概率。
线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了3个常量来表示最低,最高,以及默认优先级:
Thread.MIN_PRIORITY, Thread.MAX_PRIORITY, Thread.NORM_PRIORITY
设置优先级的方法为:
void setPriority(int priority)
守护线程
守护线程与普通线程在表现上没有什么区别,咱们只须要经过Thread提供的方法来设定便可:
void setDaemon(boolean )
当参数为true时该线程为守护线程。
守护线程的特色是,当进程中只剩下守护线程时,全部守护线程强制终止。
GC就是运行在一个守护线程上的。
须要注意的是,设置线程为后台线程要在该线程启动前设置。
Thread daemonThread = new Thread(); daemonThread.setDaemon(true); daemonThread.start();
示例代码:
/* * rose:扮演者为前台线程 */ Thread rose = new Thread(){ public void run(){ for(int i=0;i<10;i++){ System.out.println( "rose:let me go!" ); try { Thread.sleep(1000); } catch (InterruptedException e) { } } System.out.println( "rose:啊啊啊啊AAAAAaaaaa....."); System.out.println("效果:噗通!"); } }; /* * jack:扮演者后台线程 */ Thread jack = new Thread(){ public void run(){ while(true){ System.out.println( "jack:you jump!i jump!" ); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } }; //设置后台线程要在start方法调用前进行 jack.setDaemon(true); rose.start(); jack.start();
sleep方法
Thread的静态方法sleep用于使当前线程进入阻塞状态:
static void sleep(long ms)
该方法会使当前线程进入阻塞状态指定毫秒,当指定毫秒阻塞后,当前线程会从新进入Runnable状态,等待分配时间片。
该方法声明抛出一个InterruptException。因此在使用该方法时须要捕获这个异常。
例如:电子钟程序
public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss"); while(true){ System.out.println(sdf.format(new Date())); try { Thread.sleep(1000);//每输出一次时间后阻塞1秒钟 } catch (InterruptedException e) { e.printStackTrace(); } } }
注:改程序可能会出现"跳秒"现象,由于阻塞一秒后线程并不是是马上回到running状态,而是出于runnable状态,等待获取时间片。那么这段等待时间就是"偏差"。因此以上程序并不是严格意义上的每隔一秒钟执行一次输出。
yield方法
Thread的静态方法yield:
static void yield()
该方法用于使当前线程主动让出当次CPU时间片回到Runnable状态,等待分配时间片。
join方法
Thread的方法join:
void join()
该方法用于等待当前线程结束。此方法是一个阻塞方法。
该方法声明抛出InterruptException。
例如:
public static void main(String[] args) { final Thread t1 = new Thread(){ public void run(){ //一些耗时的操做 } }; Thread t2 = new Thread(){ public void run(){ try { t1.join();//这里t2线程会开始阻塞,直到t1线程的run方法执行完毕 } catch (InterruptedException e) { e.printStackTrace(); } //如下是当前线程的任务代码,只有t1线程运行完毕才会运行。 } }; }
示例代码:
//用来标示图片是否下载完毕的一个状态 public static boolean isFinish; public static void main(String[] args) { final Thread download = new Thread(){ public void run(){ System.out.println("down:开始下载图片..."); for(int i=1;i<=100;i++){ System.out.println("down:"+i+"%"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("down:图片下载完毕!"); isFinish = true; } }; Thread show = new Thread(){ public void run(){ System.out.println("show:开始显示图片!"); /* * 在这里等待download将图片下载完毕 */ try{ download.join(); }catch(InterruptedException e){ e.printStackTrace(); } if(!isFinish){ throw new RuntimeException("图片没有下载完毕!"); } System.out.println("show:显示图片完毕!"); } }; download.start(); show.start(); }
线程同步
synchronized关键字
多个线程并发读写同一个临界资源时候会发生"线程并发安全问题“
常见的临界资源: 多线程共享实例变量 多线程共享静态公共变量
若想解决线程安全问题,须要将异步的操做变为同步操做。 何为同步?那么咱们来对比看一下什么是同步什么异步。
所谓异步操做是指多线程并发的操做,至关于各干各的。 所谓同步操做是指有前后顺序的操做,至关于你干完我再干。
而java中有一个关键字名为:synchronized,该关键字是同步锁,用于将某段代码变为同步操做,从而解决线程并发安全问题。
锁机制
Java提供了一种内置的锁机制来支持原子性:
同步代码块(synchronized 关键字 ),同步代码块包含两部分:一个做为锁的对象的引用,一个做为由这个锁保护的代码块。
synchronized (同步监视器—锁对象引用){ //代码块 }
若方法全部代码都须要同步也能够给方法直接加锁。
每一个Java对象均可以用作一个实现同步的锁,线程进入同步代码块以前会自动得到锁,而且在退出同步代码块时释放锁,并且不管是经过正常路径退出锁仍是经过抛异常退出都同样,得到内置锁的惟一途径就是进入由这个锁保护的同步代码块或方法。
选择合适的锁对象
使用synchroinzed须要对一个锁对象上锁以保证线程同步。
那么这个锁对象应当注意:多个须要同步的线程在访问该同步块时,看到的应该是同一个锁对象引用。不然达不到同步效果。 一般咱们会使用this来做为锁对象。
选择合适的锁范围
在使用同步块时,应当尽可能在容许的状况下减小同步范围,以提升并发的执行效率。
静态方法锁
当咱们对一个静态方法加锁,如:
public synchronized static void xxx(){ …. }
那么该方法锁的对象是类对象。每一个类都有惟一的一个类对象。获取类对象的方式:类名.class。
静态方法与非静态方法同时声明了synchronized,他们之间是非互斥关系的。缘由在于,静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。
wait和notify
多线程之间须要协调工做。
例如,浏览器的一个显示图片的 displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。若是图片尚未下载完,displayThread能够暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,能够显示了”,这时,displayThread继续执行。
以上逻辑简单的说就是:若是条件不知足,则等待。当条件知足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。
示例代码:
public static Object obj = new Object(); //用来标示图片是否下载完毕的一个状态 public static boolean isFinish; public static void main(String[] args) { final Thread download = new Thread(){ public void run(){ System.out.println("down:开始下载图片..."); for(int i=1;i<=100;i++){ System.out.println("down:"+i+"%"); try { Thread.sleep(50); } catch (InterruptedException e) { } } System.out.println("down:图片下载完毕!"); isFinish = true; /* * 当图片下载完毕,就能够通知显示线程开始 * 显示图片 */ synchronized (obj) { obj.notify(); } //继续下载附件 System.out.println("down:开始下载附件..."); for(int i=1;i<=100;i++){ System.out.println("down:"+i+"%"); try { Thread.sleep(50); } catch (InterruptedException e) { } } System.out.println("down:附件下载完毕!"); } }; Thread show = new Thread(){ public void run(){ System.out.println("show:开始显示图片!"); /* * 在这里等待download将图片下载完毕 */ try{ // download.join(); synchronized (obj) { obj.wait(); } }catch(InterruptedException e){ } if(!isFinish){ throw new RuntimeException("图片没有下载完毕!"); } System.out.println("show:显示图片完毕!"); } }; download.start(); show.start(); }
线程安全API与非线程安全API
以前学习的API中就有设计为线程安全与非线程安全的类:
StringBuffer 是同步的 synchronized append(); StringBuilder 不是同步的 append();
相对而言StringBuffer在处理上稍逊于StringBuilder,可是其是线程安全的。当不存在并发时首选应当使用StringBuilder。
一样的:
Vector 和 Hashtable 是线程安全的而ArrayList 和 HashMap则不是线程安全的。
对于集合而言,Collections提供了几个静态方法,能够将集合或Map转换为线程安全的:
例如:
Collections.synchronizedList() :获取线程安全的List集合 Collections.synchronizedMap():获取线程安全的Map ... List<String> list = new ArrayList<String>(); list.add("A"); list.add("B"); list.add("C"); list = Collections.synchronizedList(list);//将ArrayList转换为线程安全的集合 System.out.println(list);//[A,B,C] 能够看出,原集合中的元素也得以保留 ...
使用ExecutorService实现线程池
当一个程序中若建立大量线程,并在任务结束后销毁,会给系统带来过分消耗资源,以及过分切换线程的危险,从而可能致使系统崩溃。为此咱们应使用线程池来解决这个问题。
ExecutorService是java提供的用于管理线程池的类。
线程池有两个主要做用: 控制线程数量 重用线程
线程池的概念:首先建立一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但能够同时向一个线程池提交多个任务
线程池有如下几种实现策略: Executors.newCachedThreadPool() 建立一个可根据须要建立新线程的线程池,可是在之前构造的线程可用时将重用它们。 Executors.newFixedThreadPool(int nThreads) 建立一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。 Executors.newScheduledThreadPool(int corePoolSize) 建立一个线程池,它可安排在给定延迟后运行命令或者按期地执行。 Executors.newSingleThreadExecutor() 建立一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
能够根据实际需求来使用某种线程池。例如,建立一个有固定线程数量的线程池:
... ExecutorService threadPool = Executors.newFixedThreadPool(30);//建立具备30个线程的线程池 Runnable r1 = new Runable(){ public void run(){ //线程体 } }; threadPool.execute(r1);//将任务交给线程池,其会分配空闲线程来运行这个任务。 ...
使用BlockingQueue
BlockingQueue是双缓冲队列。
在多线程并发时,若须要使用队列,咱们可使用Queue,可是要解决一个问题就是同步,但同步操做会下降并发对Queue操做的效率。
BlockingQueue内部使用两条队列,可容许两个线程同时向队列一个作存储,一个作取出操做。在保证并发安全的同时提升了队列的存取效率。
双缓冲队列有一下几种实现:
ArrayBlockingDeque:规定大小的BlockingDeque,其构造函数必须带一个int参数来指明其大小.其所含 的对象是以FIFO(先入先出)顺序排序的。 LinkedBlockingDeque:大小不定的BlockingDeque,若其构造函数带一个规定大小的参数,生成的 BlockingDeque有大小限制,若不带大小参数,所生成的BlockingDeque的大小Integer.MAX_VALUE 来决定.其所含的对象是以FIFO(先入先出)顺序排序的。 PriorityBlockingDeque:相似于LinkedBlockDeque,但其所含对象的排序不是FIFO,而是依据对象的 天然排序顺序或者是构造函数的Comparator决定的顺序。 SynchronousQueue:特殊的BlockingQueue,对其的操做必须是放和取交替完成的。
例如:
public static void main(String[] args) { BlockingQueue<String> queue = new LinkedBlockingDeque<String>(); try { //queue.offer("A");//当即向队列末尾追加元素 /* * 向队列末尾追加元素,指定可延迟5秒。 * 若5秒钟内成功将元素加入队列返回true * 若超时后元素仍然没有加入队列则返回flase */ queue.offer("A",5,TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(queue.poll()); }