一 Java中的值传递和引用传递
1.概念说明
当一个对象被当作参数传递到一个方法后,在此方法内可以改变这个对象的属性,那么
这里到底是“按值传递”还是“按引用传递”?
答:是按值传递。Java 语言的参数传递只有“按值传递”。当一个实例对象作为参数被传
递到方法中时,参数的值就是该对象的引用的一个副本。指向同一个对象,对象的
内容可以在被调用的方法内改变,但对象的引用(不是引用的副本) 是永远不会改变
的。
2.一些例题
(1)参数为基本数据类型
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
运行结果是:
a = 20
b = 10
num1 = 10
num2 = 20
解析:在swap方法中,a、b的值进行交换,并不会影响到num1、num2。因为,
a、b中的值,只是从num1、num2的复制过来的。 也就是说,a、b相当
于num1、num2的副本,副本的内容无论怎么修改,都不会影响到原件本
身。
(2)参数为数组
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
change(arr);
System.out.println(arr[0]);
}
//将数组的第一个元素变为0
public static void change(int[] array) {
int len = array.length;
array[0] = 0;
}
运行的结果是:
0
解析:调用change()的时候,形参array接收的是arr地址值的副本。并在
change方法中,通过地址值,对数组进行操作。change方法弹栈以后,
数组中的值已经改变。main方法中,打印出来的arr[0]也就从原来的1变
成了0。
(3)参数为string
public static void main(String[] args) {
String str = "AAA";
change(str);
System.out.println(str);
}
public static void change(String s) {
s = "abc";
}
运行的结果是:
AAA
解析:String对象做为参数传递时,走的依然是引用传递,只不过String这个类
比较特殊。 String对象一旦创建,内容不可更改。每一次内容的更改都
是重现创建出来的新对象。当change方法执行完毕时,s所指向的地址
值已经改变。而s本来的地址值就是copy过来的副本,所以并不能改变
str1的值。
更细致的说明:
String的API中有这么一句话:“their values cannot be changed after they
are created”, 意思是:String的值在创建之后不能被更改。
API中还有一段:
String str = "abc";
等效于:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
也就是说:对String对象str的任何修改 等同于 重新创建一个对象,并将新
的地址值赋值给str。
二.java中一些自己搞不明白的东西
1.Java代码的执行顺序
(1)Java程序初始化的3个原则
(a)静态对象(变量) > 非静态对象(变量):静态对象(变量)只是初始化
一次,而非静态对象(变量)可以初始化很多次;
(b)父类 >子类;
(i)父类的静态对象(成员变量)和静态代码块;
(ii)子类的静态对象(成员变量)和静态代码块;
(iii)父类非静态对象(成员变量)和非静态代码块;
(iv)父类构造函数;
(v)子类的非静态对象(成员变量)和非静态代码块;
(vi)按照成员变量的定义顺序进行初始化
(c)执行顺序的优先级:
静态块>main()>构造块>构造方法。
这里没说非main的静态方法,是因为它和普通方法一样,只有在调用时候
才执行(这部门需要做些例题)。
关于一些概念的解释:
静态块:用static声明;JVM加载类的时候执行,仅会执行一次(静态代码
块按照声明顺序执行);静态代码块的作用完成类的初始化;
构造块:类中直接用{}定义;每一次创建对象的时候执行;
(2)关于java代码执行顺序的一些例题:
例1:
package com.niuke;
public class Test {
public static Test t1 = new Test();
{
System.out.println("111");
}
static{
System.out.println("222");
}
public static void main(String[] args){
Test t2 = new Test();
}
}
运行结果:
111
222
333
解释:
第一步,在调用main方法前先装载Test类,装载Test.class,装载时按顺序做
静态成员初始化,即先实例化t1,实例化t1的子过程是“执行构造代码
块,打印111(由于静态方法只会在第一次加载类时初始化,无论实
例化多少次,静态方法只初始化一次,所以只有方法块blockA会打
印)”。
第二步,执行静态代码块,打印222
第三步,后面实例化 t2,执行构造代码块,打印111
例2:
class Code{
{
System.out.println("Code的构造块");
}
static{
System.out.println("Code的静态代码块");
}
public Code(){
System.out.println("Code的构造方法");
}
}
public class CodeBlock03{
{
System.out.println("CodeBlock03的构造块");
}
static{
System.out.println("CodeBlock03的静态代码块");
}
public CodeBlock03(){
System.out.println("CodeBlock03的构造方法");
}
public static void main(String[] args){
System.out.println("CodeBlock03的主方法");
new Code();
new Code();
new CodeBlock03();
new CodeBlock03();
}
}
运行结果:
CodeBlock03的静态代码块
CodeBlock03的主方法
Code的静态代码块
Code的构造块
Code的构造方法
Code的构造块
Code的构造方法
CodeBlock03的构造块
CodeBlock03的构造方法
CodeBlock03的构造块
CodeBlock03的构造方法
解析:
执行顺序的优先级:静态块>main()>构造块>构造方法
下面看几个由例2演变出来的例子
例2.1
public class CodeBlock01{
public static void main(String[] args){
{
int x=3;
System.out.println("1,普通代码块内的变量x="+x);
}
int x=1;
System.out.println("主方法内的变量x="+x);
{
int y=7;
System.out.println("2,普通代码块内的变量y="+y);
}
}
}
运行结果:
1,普通代码块内的变量x=3
主方法内的变量x=1
2,普通代码块内的变量y=7
例2.2
public class CodeBlock02{
{
System.out.println("第一代码块");
}
public CodeBlock02(){
System.out.println("构造方法");
}
{
System.out.println("第二构造块");
}
public static void main(String[] args){
new CodeBlock02();
new CodeBlock02();
new CodeBlock02();
}
}
运行结果:
第一代码块
第二构造块
构造方法
第一代码块
第二构造块
构造方法
第一代码块
第二构造块
构造方法
例3:
class Test2_Extends {
public static void main(String[] args) {
Zi z = new Zi();
}
/*
1. jvm调用了main方法,main方法进栈,因为存在继承关系,所以父类要先加载
2. new Zi();会先将Fu.class和Zi.class分别加载进内存,再创建对象.当Fu.class加载进内存,父类的静态代码块会随着Fu.class一起加载,
当Zi.class加载进内存,子类的静态代码块会随着Zi.class一起加载
--> 第一个输出,静态代码块Fu,第二个输出静态代码块Zi
3,走Zi类的构造方法,因为java中是分层初始化的,先初始化父类,再初始化子类,所以先走的父类构造,
但是在执行父类构造时,发现父类有构造代码块,构造代码块是优先于构造方法执行的
-->所以第三个输出构造代码块Fu,第四个输出构造方法Fu
4,Fu类初始化结束,子类初始化,第五个输出的是构造代码块Zi,构造方法Zi
*/
}
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
另外一种说明,如果第一种看不懂代码注释中的解释,可以看下面的解释,代码
注释中的若看懂就不用看下面的解释
(a)在初次new一个Child类对象时,发现其有父类,则先加载Parent类,再
加载Child类。
(b)加载Parent类:
初始化Parent类的static属性,赋默认值;
执行Parent类的static初始化块;
(c)加载Child类:
初始化Child类的static属性,赋默认值;
执行Child类的static初始化块;
(d)创建Parent类对象:
初始化Parent类的非static属性,赋默认值;
执行Parent类的instance初始化块;
执行Parent类的构造方法;
(e)创建Child类对象:
初始化Child类的非static属性,赋默认值;
执行Child类的instance初始化块;
执行Child类的构造方法;
若 后面再创建Child类对象时,就按照顺序执行(4)(5)两步。
(3)Java对象的创建过程
(a)使用new关键字,具体过程如上面所讲
(b)运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类
的newInstance()实例方法。
(c)调用对象的clone()方法
(d) 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()
方法。
序列化:将对象状态转化为可保持或传输的格式的过程,被序列化的对
象必须implments Serializable
反序列化:将流转化成对象的过程
当两个进程在进行远程通信时,彼此可以发送各种类型的数
据。无论是何种类型的数据,都会以二进制序列的形式在网
络上传送。发送方需要把这个Java对象转换为字节序列,即
java对象序列,才能在网络上传送,即序列化过程;接收方
则需要把字节序列再恢复为java对象,即反序列化。
2.static
static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类)
(1)static变量和非static变量的区别
(a)类变量(static修饰的成员变量)在类加载的时候初始化;非static修饰的
成员变量是在对象new出来的时候初始化。
(b)static变量与类关联,不与具体的对象绑定在一起,该存储空间被类的所
有实例共享,在方法区仅加载一次,而非static在创建对象时会加载很多
次,每次创建都会拷贝一份。
(c)在类外,对象引用static变量是通过类名.变量名调用,对象在引用非static
变量时通过对象名.方法名调用;在类内调用static变量时以类名.变量名
的方式调用,调用非sttatic变量时用this或直接调用。
(2)static方法和非static方法
(a)static方法是加载一次,被所有的对象所共享。而非静态方法是有多少个
对象就拷贝多少次,每个对象只能调用自己的拷贝的方法。
(b)对象调用非静态的方法时,不考虑线程安全性的问题,而调用静态方法
时,要考虑安全性的问题。因为静态方法只有一份,所有对象共享。而
非静态方法是每个对象有一份。
(c)static方法可以用对象.方法名来调用,也可以用类名.方法名来调用。而
非static方法只能创建对象后时调用。
(d)同一个类中,静态方法中只能访问类中的静态成员。而非静态方法可以
访问非静态的方法。
(3)Java中没有static局部变量
(4)static方法能否被重写
答不能。
对于属性和静态方法来说,调用取决于声明的类型,而对于其他的取决于运行时的类型。
3.final
(1)final可以修饰:属性,方法,类,局部变量(方法中的变量)。
(2) final标记的类不能被继承
(3)final标记的方法不能被子类重写。
(4) final标记的变量(成员变量或局部变量)即成为常量,只能赋值一次。
final 标记的成员变量必须在声明的同时赋值,如果在声明的时候没有赋
值,那么只有一次赋值的机会,而且只能在构造方法中显式赋值,然后
才能使用;final标记的局部变量可以只声明不赋值,然后再进行一次性
的赋值。
基本类型:基本类型的值不能改变。
引用类型:引用类型的地址不能发生改变,但是堆内存的值是可以改变
的。
4.static final和final static
(1)static final和final static没什么区别,一般static写在前面。
(2)static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访
问。
(3)static final也可以修饰方法,表示该方法不能重写,可以在不new对象的
情况下调用。
5. equals和"=="
Object的equals的源码如下
public boolean equals(Object obj) {
return (this == obj);
}
由equals的源码可以看出这里定义的equals与==是等效的(Object类中的
equals没什么区别),不同的原因就在于有些类(像String、Integer等类)
对equals进行了重写,但是没有对equals进行重写的类(比如我们自己写
的类)就只能从Object类中继承equals方法,其equals方法与==就也是等
效的,除非我们在此类中重写equals。
package com.jsonmappertest.jsonmappertest;
public class bb {
Person p=new Person();
public static void main(String[] args)
{
Person p1=new Person();
Person p2=new Person();
Person p3=p1;
String s1=new String("aa");
String s2=new String("aa");
String t1="aa";
String t2="aa";
System.out.println("p1.equalse(p2):"+p1.equals(p2));
System.out.println("p1==p2:"+(p1==p2));
System.out.println("(s1.equals(s2):"+s1.equals(s2));
System.out.println("s1==s2:"+(s1==s2));
System.out.println("t1.equals(t2):"+(t1.equals(t2)));
System.out.println("t1==t2:"+(t1==t2));
System.out.println("p3.equals(p1):"+p3.equals(p1));
System.out.println("p3==p1:"+(p3==p1));
}
}
执行结果:
p1.equalse(p2):false
p1==p2:false
(s1.equals(s2)true
s1==s2:false
t1.equals(t2):true
t1==t2:true
p3.equals(p1):true
p3==p1:true
5.Java中的线程安全
静态变量:线程非安全。静态变量即类变量,位于方法区,为所有对象共享,
共享一份内存,一旦静态变量被修改,其他对象均对修改可见,
故线程非安全。
实例变量:单例模式(只有一个对象实例存在)非线程安全,非单例线程安
全。实例变量为对象实例私有,在虚拟机的堆中分配,若在系统
中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量
那样,被某个线程修改后,其他线程对修改均可见,故线程非安
全;如果每个线程执行都是在不同的对象中,那对象与对象之间
的实例变量的修改将互不影响,故线程安全。
局部变量:线程安全。每个线程执行时将会把局部变量放在各自栈帧的工
作内存中,线程间不共享,故不存在线程安全问题。
补充一点:成员变量就是实例变量+静态变量
有状态的对象和无状态的对象:
有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例
变量的对象 ,可以保存数据,是非线程安全的。
无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),
就是没有实例变量的对象。不能保存数据,是不变类,是线程安全的。
6.内部类
Java 中允许一个类的内部定义另一个类,后者成为内部类。
(1)内部类可体现逻辑上的从属关系,同时对于其他类可以控制内部类对外
不可见。
(2)外部类的成员变量的作用域是整个外部类,包括内部类。但外部类不能
访问内部类的 private 成员
(3)逻辑上相关的类可以在一起,可以有效的实现信息隐藏。)内部类可以
直接访问外部类的成员,可以用此实现多继承。
(4)匿名内部类
public void t1(){
final int a = 15;
String a = “t1”;
new Aclass(){
public void testA(){
System.out.println(TOTAL_NUMBER);
System.out.println(id);
System.out.println(a);
}
}.testA();
}
public void t1(){
Class B extends Aclass{
public void testA(){
System.out.println(TOTAL_NUMBER);
System.out.println(id);
System.out.println(a);
}
new B().testA();
}
}
匿名内部类规则:
(a)匿名类没有构造方法;
(b)匿名类不能定义静态的成员;
(c)匿名类不能用4 种权限、static、final、abstract修饰;
(d)只可以创建一个匿名类实例
7.枚举
(1)枚举的一些简介
在JDK1.5 之前,我们定义常量都是: public static fianl.... 。现在好了,
有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了
比常量更多的方法。就是说以前定义的常量都能写到枚举里面了。
使用常量的缺陷:
(i)类型不安全。若一个方法中要求传入季节这个参数,用常量的
话,形参就是int类型,开发者传入任意类型的int类型值就行,
但是如果是枚举类型的话,就只能传入枚举类中包含的对象。
(ii)没有命名空间。开发者要在命名的时候以SEASON_开头,这
样另外一个开发者再看这段代码的时候,才知道这四个常量分
别代表季节。
(a)先看一个简单的枚举类:
package enumcase;
public enum SeasonEnum {
SPRING,SUMMER,FALL,WINTER;
}
(i)enum和class、interface的地位一样
(ii)使用enum定义的枚举类默认继承了java.lang.Enum,而不是继
承Object类。枚举类可以实现一个或多个接口。
(iii)枚举类的所有实例都必须放在第一行展示(就是上例中的
SPRING,SUMMER,FALL,WINTER;),不需使用new 关键字,
不需显式调用构造器。自动添加public static final修饰。
(iv)使用enum定义、非抽象的枚举类默认使用final修饰,不可以被
继承。枚举类的构造器只能是私有的。
(b)枚举类内也可以定义属性和方法,可以是静态的和非静态的。
package enumcase;
public enum SeasonEnum {
SPRING("春天"),SUMMER("夏天"),FALL("秋天"),WINTER("冬天");
private final String name;
private SeasonEnum(String name)
{
this.name = name;
}
public String getName() {
return name;
}
}
实际上在第一行写枚举类实例的时候,默认是调用了构造器的,所以
此处需要传入参数,因为没有显式申明无参构造器,只能调用有参数
的构造器。构造器需定义成私有的,这样就不能在别处申明此类的对
象了(只能在该枚举类型的内部)。枚举类通常应该设计成不可变
类,它的Field不应该被改变,这样会更安全,而且代码更加简洁。所
以我们将Field用private final修饰。
(c)枚举类可以实现一个或多个接口。与普通类一样,实现接口的时候需
要实现接口中定义的所有方法,若没有完全实现,那这个枚举类就是
抽象的,只是不需显式加上abstract修饰,系统化会默认加上。
package enumcase;
public enum Operation {
PLUS{
@Override
public double eval(double x, double y) {
return x + y;
}
},
MINUS{
@Override
public double eval(double x, double y) {
return x - y;
}
},
TIMES{
@Override
public double eval(double x, double y) {
return x * y;
}
},
DIVIDE{
@Override
public double eval(double x, double y) {
return x / y;
}
};
/**
* 抽象方法,由不同的枚举值提供不同的实现。
* @param x
* @param y
* @return
*/
public abstract double eval(double x, double y);
public static void main(String[] args) {
System.out.println(Operation.PLUS.eval(10, 2));
System.out.println(Operation.MINUS.eval(10, 2));
System.out.println(Operation.TIMES.eval(10, 2));
System.out.println(Operation.DIVIDE.eval(10, 2));
}
}
(d)switch语句里的表达式可以是枚举值
Java5新增了enum关键字,同时扩展了switch。
package enumcase;
public class SeasonTest {
public void judge(SeasonEnum s)
{
switch(s)
{
case SPRING:
System.out.println("春天适合踏青。");
break;
case SUMMER:
System.out.println("夏天要去游泳啦。");
break;
case FALL:
System.out.println("秋天一定要去旅游哦。");
break;
case WINTER:
System.out.println("冬天要是下雪就好啦。");
break;
}
}
public static void main(String[] args) {
SeasonEnum s = SeasonEnum.SPRING;
SeasonTest test = new SeasonTest();
test.judge(s);
}
}
(2)总结一下枚举的用法
(a)常量
public enum Color {
RED, GREEN, BLANK, YELLOW
}
(b)switch
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch(color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
(c)向枚举中添加新方法
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 普通方法
public static String getName(int index) {
for (Color c : Color.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
// get set 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
(d)覆盖枚举的方法,下面给出一个toString()方法覆盖的例子
public enum Color
{
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//覆盖方法
@Override
public String toString() {
return this.index+"_"+this.name;
}
}
(e)实现接口
public interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour
{
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//接口方法
@Override
public String getInfo() {
return this.name;
}
//接口方法
@Override
public void print() {
System.out.println(this.index+":"+this.name);
}
}
(f)使用接口组织枚举
public interface Food {
enum Coffee implements Food{
BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
}
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
}
(g)关于枚举集合的使用
java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保
证集合中的元素不重复;EnumMap中的 key是enum类型,而value则
可以是任意类型。关于这个两个集合的使用就不在这里赘述,可以参
考JDK文档。
8.I/O流
(1)流的分类
(a)输入流和输出流
我们把从外部设备流向程序的流成为输入流
把从程序流向外部设备的流称为输出流。
(b)字符流和字节流
根据数据在Stream里的最小传输单位, 我们也可以把流分为两类:
字符流:最小传输单位为1个字符(java里的字符不再用ASCII码表示,
而是用万国码, 所以1个字符
(char) = 2个字节(byte) = 16bit(位)).
字节流:最小传输单位为1个字节(byte)。
它们最大的区别就是字符流只能读写文本格式的外部设备,而字节
流可以读写所有格式的外部设备(例如2进制文件,多媒体文件等).
(c)节点流和处理流(原始流和包裹流)
Java里的stream还可以嵌套,按照流的功能还可以分为节点流和处
理流。
节点流:也叫原始流, 用于传输基本数据的流。
处理流:也叫包裹流, 包裹在节点流之上, 对节点流的数据作进一步
处理的流, 处理流的前提是具有节点流。处理流可以多重嵌
套。
(2)Java里四个基本流
InputStream:输入字节流,也就是说它既属于输入流,也属于字节流。
OutputStream:输出字节流,既属于输出流,也属于字节流。
Reader: 输入字符流,既属于输入流,又属于字符流。
Writer: 输出字符流,既属于输出流,又属于字符流。
这个4个流都是虚拟类, 也就是说它们不能直接被实例化。
(a)Reader流及其常用方法
Reader流属于输入流和字符流,也就是说Reader流的作用是从外部设
备传输数据到程序,而且最小单位是1个字符。
(i)int read() throws IOException
read()方法可以讲是Reader最基础的一个方法, 它的作用是从流中
读取1个字符, 并把这个字符存储到1个整形(int)变量中。如果读到
了输入流的末尾(最后1个字符) 则返回 -1。
我们知道字符类型是char, 为何返回的是1个int类型?
原因就是为了接口统一, 实际上read()方法会将接收到的char类型
的数据存储在Int类型的较低位2个字节中。
注意,因为网络, 设备等外部原因可能会导致读取失败, 这个read()
方法throws IOException,使用时要捕捉。
(ii)int read(char[] charbuffer) throws IOException
是不是觉得上面的方法一次读1个字符是不是很蛋疼, 觉得有点浪
费内存的嫌疑啊。所以Reader 流提供了另1个方法,可以1次个读
取若干个字符。该方法返回的是参数数组接受到的字符个数,假
如读取到外部设备的结尾,则返回-1。
例如执行一次下面的语句:
len = ReaderA.read(cbuffer);
那么程序就会从ReaderA这个流中读取一次若干(len)个字符放入
到字符数组cbuffer中,len就是具体读取的字符数据个数。
通常来讲, 这个len就是参数字符串的长度. 也就是说一般来讲
int read(char[])方法会填满这个字符串。但是也有例外,如:
内存紧张
读取到最后的部分, 例如外部设备中有89个字符, 参数字符串的
长度是20, 那么前面4次, 都是读20个字符, 最后那次就只读9个字
符。
读到外部设备的结尾, 返回-1。
返回值len的意义?
int read(char[]) 执行一次新的读取时,并不会擦除参数字符数组的
原有数据,而读取的字符数不是确定的(上面解析了3个原因),所以
我们需要返回值len来确定,数组内那些字符是有效的, 那些字符是
之前读取的,所以,我们需要返回值len来在参数字符串中提取有效
数据。
(iii) int read(char[] charbuffer, int offset, int length) throws IOException
参数charbuffer, 也是用于存放接收到的字符数据;
参数offset,表示是从第offset个位置开始存放接收到的数据。也就
是说在那一次读取方法中, 该数组中前offset的数据很可能是以前的
数据。每次接受的个数不能大于第三个参数length,也就是执行1次
read,最多读取length,当然,也不能大于数组参数charbuffer的长
度,所以 length设置大于charbuffer的长度是无意义的。
返回值:实际接受到的字符个数。
(iv) long skip(long n) throws IOException
尝试跳过n个字符不读,返回实际跳过的字符数,比较少用啦。
(v) void close() throws IOException
每次用完流时都调用该方法,用于关闭流(注:应该写到finally语句
块里)。
(b)Writer流及其常用方法
Writer流属于输入流和字符流,也就是说Writer流的作用是从程序到外部
文本文件,而且最小单位是1个字符。
(i) void write(int c) throws IOException
将1个字符通过输出流写到输出流(未到达为外部设备)中。值得注意
的是,传入的参数字符(char)数据是存放到1个整形(int)变量中。而
且是放在整形变量的低位2个字节中(实际上在jdk1.7中提供了以
char类型作为参数的重载方法)。
未到达为外部设备中意思是:当执行了这个方法,字符数据并没有
立即写入到外部设备,而是保存在输出流的缓冲区中(还在内存中)。
import java.io.*;
public class Writer1 {
public static void f() {
int i;
String s = new String("Just a testing for writer stream!\n");
File fl = new File("/home/gateman/tmp/testwriter1.txt");
if (fl.exists()) {
fl.delete();
}
try {
fl.createNewFile();
} catch (IOException e) {
System.out.println("File created failed!");
}
System.out.println("File created!");
FileWriter fw = null;
try {
fw = new FileWriter(fl);
} catch (IOException e) {
System.out.println("Create filewriter failed!");
if (null != fw) {
try {
fw.close();
} catch (IOException e1) {
System.out.println("Close the filewriter failed!");
return;
}
System.out.println("Close the filewriter successfully!");
}
return;
}
for (i = 0; i < s.length(); i++) {
try {
fw.write((int) s.charAt(i));
} catch (IOException e) {
System.out.println("error occurs in fw.write()!");
}
}
System.out.println("done!");
}
}
可以见到实际场程序能正常执行完最后一行代码,文件也创建成功
了,但是文件是空的。因为输出流在缓冲区的数据并未写入到文件
那么如何把输出流缓冲区的数据写入到外部设备答案很简单,执行
流的关闭close()方法即可。加上close()的代码如下:
import java.io.*;
public class Writer1 {
public static void f() {
int i;
String s = new String("Just a testing for writer stream!\n");
File fl = new File("/home/gateman/tmp/testwriter1.txt");
if (fl.exists()) {
fl.delete();
}
try {
fl.createNewFile();
} catch (IOException e) {
System.out.println("File created failed!");
}
System.out.println("File created!");
FileWriter fw = null;
try {
fw = new FileWriter(fl);
} catch (IOException e) {
System.out.println("Create filewriter failed!");
if (null != fw) {
try {
fw.close();
} catch (IOException e1) {
System.out.println("Close the filewriter failed!");
return;
}
System.out.println("Close the filewriter successfully!");
}
return;
}
for (i = 0; i < s.length(); i++) {
try {
fw.write((int) s.charAt(i));
} catch (IOException e) {
System.out.println("error occurs in fw.write()!");
}
}
try {
fw.close();
} catch (IOException e) {
System.out.println("Close the filewriter failed!");
return;
}
System.out.println("Close the filewriter successfully!");
System.out.println("done!");
}
}
(ii)void flush() throws IOException
根据上面的例子会知道, write()方法写入的数据未必回立即写入到外
部设备,而是有1个缓冲区存储着它们。当close()方法成功时, 才会
保证所有缓冲区的内容都写入到了外部设备。但是close()方法一般
放在1个事务的最后部分执行,那么中间出现了异常或错误导致程序
跳出时,就很可能回丢失缓冲区的数据了。那么有无1个类似与保存
的功能,让程序猿在这个期间强制把缓冲区的数据写入到外部设备
中?答案就是有的,这个flush()方法的作用就是把当前缓冲区所有的
数据写入到外部设备。
(iii)void write(char[] cbuffer) throws IOException
把1个字符数组的所有数据写入到输出流缓冲区。
(iv)void write(String s) throws IOException
把整个字符串的内容写入到输出流缓冲区。
(v)void write(String s, int offset, int length) throws IOException
把字符串的一部分写入输出流缓冲区
(c)InputStream及其常用方法
字节输入流,与Reader最大的区别就是它不但支持文本外部设备,还支
持2进制外部设备。这里用的例子是FileInputStream,顾名思义,就是
搭向磁盘文件的字节输入流
(i)int read() throws IOException
读取1个字节(2进制),并以整数形式(10进制)返回,如果读到流的末
尾,则返回-1。
例子:
import java.io.*;
public class InputStream1{
public static void f(){
FileInputStream fis = null;
try{
fis = new FileInputStream("/home/gateman/tmp/build.xml");
}catch(FileNotFoundException e){
System.out.println("file not found!");
return;
}
int bt; //byte
try{
bt = fis.read();
while(bt > -1){
System.out.printf("%c",(char)bt);
bt = fis.read();
}
}catch(IOException e){
System.out.println("IOException!");
e.printStackTrace();
}finally{
if (null != fis){
System.out.println("============");
try{
fis.close();
}catch(IOException e){
System.out.println("Stream close failed!");
return;
}
System.out.println("Stream close successfully!");
}
}
}
}
(ii) int available() throws IOException
返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取
(或跳过)的估计剩余字节数。下一次调用可能是同一个线程,也可
能是另一个线程。一次读取或跳过此数量个字节不会发生阻塞,但
读取或跳过的字节可能小于该数。简答地讲,这个方法返回输入流
中还有多少剩余字节数。如果是在连接磁盘文件的输入流中,一般
就是返回磁盘文件的(剩余)大小。但是这个方法在网络流中更有意
义,例如网络上1个流连接两个程序传输,1个程序往往要等待另1个
程序向流发出数据,那么这个方法就可以用于判断流中是否存在数据
了!这个方法用在从本地文件读取数据时,一般不会遇到问题,但如
果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,
对方明明发来了1000个字节,但是自己的程序调用available()方法却
只得到900,或者100,甚至是0,感觉有点莫名其妙,怎么也找不到
原因。其实,这是因为网络通讯往往是间断性的,一串字节往往分几
批进行发送。本地程序调用available()方法有时得到0,这可能是对方
还没有响应,也可能是对方已经响应了,但是数据还没有送达本地。
对方发送了1000个字节给你,也许分成3批到达,这你就要调用3次
available()方法才能将数据总数全部得到。
(iii)int read(byte[] b) throws IOException
读取一定数量的字节,并存储在字节数组b中,返回实际读取的字
节数,如果读到输出流的末尾,则返回-1
(iii) int read(byte[] b, int offset, int length) throws IOException
上面的方法的重载,读取一定量的字节,存放在数组b的特定位置
(d)OutputStream及其常用方法
输出字节流
(i)void write(int b) throws IOException
向输出流缓冲区中写入1个byte的数据, 该字节数据为参数整型b的
低8位。
(ii)void write(byte[] b) throws IOException
将1个字节数组b的内容写入输入流缓冲区
(iii)void write(byte[] b, int offset, int length) throws IOException
将字节数组b的部分内容写入输入流缓冲区
(iv)void flush() throws IOException
立即将缓冲区的数据写入到外部设备。
9.socket
socket是对TCP/IP协议的封装,它的出现只是使得程序员更方便地使用TCP/IP
协议栈而已。socket本身并不是协议,它是应用层与TCP/IP协议族通信的中间
软件抽象层,是一组调用接口(TCP/IP网络的API函数)
10.泛型
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型
三.Java函数
1.SimpleDateFormat
(1)setLenient(true/false)
此方法用来标志是否严格解析日期。其实很简单,就是如果你输入的日期
不合法,它会不会先进行一定的计算,计算出能有合法的值,就以计算后
的值为真正的值。比如说当你使用的时候有2012-02-31,2012-14-03这样
数据去format,如果让setLenient(true),那么它就会自动解析为
2012-03-02和2013-02-03这样的日期。如果改为setLenient(false)就会让
这样出现解析异常,因为去掉了计算,而这样的数据又是不合法的,所以
出现异常也是合理的。