Java学习笔记

 Java编译器编译的是源文件而Java解释器解释的是不是文件

  1. System.out.println()的含义:System是类,out是System类的一个静态成员变量同时又是PrintStream类(该类是FilterOutputStream类的子类)的对象,它的类型是static PrintStream类型的,而PrintStream也是一个类,println是out的一个方法
  2. Java中区分大小写
  3. Java中设置path环境变量时,必须把自己的jdk放在前面
  4. 在CMD窗口中输入start可以开启一个新的窗口
  5. 在Java中byte类型是有符号整数,而在C/C++中是无符号整数,范围是-128~+127
  6. 在Java中定义数组时不能分配空间,只能先声明后分配,这与C语言不同,例如:int num[3]; (x) 只能int num[]=new int[3];
  7. 一维数组和二维数组在赋初值时,方括号内不要注明数组的长度,例如:int[] num=new int[]{1,2,3};(一维数组静态初始化) int[][] num=new int[][]{{1,2,3},{4,5,6}};(二维数组静态初始化)或int[][] num={{1,2,3},{4,5,6}};
  8. 定义不规则数组的方法:int[][] num={{1,2,3},{4,5},{6}};这是定义了一个第一行有3个元素,第二行有2个元素,第三行有1一个元素的不规则数组,或者采用int[][] num=new num[3][]; num[0]=new int[3]; num[1]=new int[2]; num[2]=new int[1];
  9. 在编程时,可以使用算术左移(<<)算术右移(>>)的方式表示乘以2或者除以2的倍数,逻辑右移(>>>)不遵循此约定
  10. 在编程时,同时按下Alt+Shift+Tab键在各个程序间切换
  11. Java为我们提供了一个反编译工具Javap.exe,此工具位于java-root\bin目录中,这个反编译工具能反编译字节码文件,将成员变量和成员方法的详细信息提供给我们,Javap加载的是一个,而不是字节码文件
  12. 使用this可以简化构造函数的调用,例如可以使用this(x,y)在缺省参数的构造方法中调用带有参数的构造方法,并且this语句必须作为第一条语句
  13. Java中的类方法类加载的时候就分配了内存空间,它只属于一个类,而不属于类的对象
  14. 在Java的实例方法内可以引用类方法和类变量,反之不可以,在静态方法中不能调用非静态方法和引用非静态成员变量
  15. 在Java中使用final定义常量,final常量可以在声明的同时赋初值,也可以在构造函数中赋初值,但在声明静态常量必须赋初值
  16. 在Java的源文件中含有多个类,则使用Javac编译时会产生多个字节码文件,在使用Java解释器加载类的时候应该加载包含main函数的类
  17. 在Java中,方法的重载发生在一个类中,而方法的覆盖发生在父类与子类之间
  18. 每个子类构造方法的第一条语句,都是隐含地调用super()[父类构造方法],如果父类没有这种形式的构造函数,那么在编译的时候就会报错
  19. 在Java中子类继承父类时构造方法不能被继承,在子类中要想调用父类的构造方法只能使用super(x,y)的形式
  20. 在Java中使用instanceof来判断一个对象是否是一个类的实例,格式为:if(对象 instanceof 类名)注意:子类对象可以是父类的实例,但是父类对象不能是子类的实例
  21. Java中环境变量的设置可以使用%classpath%的方式,保留原有的设置,例如:set classpath=%classpath%;d:;
  22. 编译并生成包的格式:javac –d 目录名(.\目录) java源文件名
  23. 在Java中无需导入Java.lang.*中的类
  24. final修饰的类为最终的类,不能被其他的类继承,例如:String类就是用final修饰的类,它不能被继承
  25. 为了确保某个函数的行为在继承过程中保持不变,并且不能被覆盖(overridden),可以使用final方法
  26. 对于一个子类当他从一个抽象类派生出来的时候,如果他没有实现基类中的抽象方法,那么对于子类来说他也成为了一个抽象类(如果一个子类没有实现抽象基类中所有的抽象方法,则子类也成为一个抽象类)
  27. native方法是用户在Java中可以使用,但不能编写的方法
  28. 在Java.lang包中有一个Object类(它是Java中所有类的祖先类,当一个类没有从其他类中派生时,那么这个类就从Object类中派生),该类中有一个方法——finalize方法(修饰符为protected void),功能类似于C++中的析构函数,对象在被回收之前会调用对象的finalize()方法,垃圾回收线程低优先级线程,System.gc(),Runtime.gc()(gc()方法是静态方法),只是告诉JVM赶紧做垃圾回收,但JVM并不是立刻就来回收,回收时刻还是不确定的,就像垃圾箱满了,你打电话叫垃圾站来回收,但并不是垃圾站马上就会来,只是几率增大了
  29. 在Java.lang包中有一个System类,该类中有一个方法——gc()方法(修饰符为static void是一个静态方法),该方法可以运行垃圾回收器,调用方式为System.gc(),直接以类名的方式调用
  30. Java的接口中所有的方法都是抽象的,即接口中所有的方法都是public abstract (默认如此),所有数据成员的修饰符为public static final
  31. Java中类的定义使用class,类的继承和接口的继承使用extends,接口的定义使用interface,接口的实现使用implements,而且接口的定义中只有方法的声明,没有方法的实现,方法的实现要放到一个类在实现这个接口的时候去定义
  32. Java类中实例方法的默认访问权限是friendly,当一个类要实现接口中定义的方法时,类中方法实现时使用的修饰符必须为public
  33. Java中一个类要实现接口时,如果要用这个类去实例化一个对象,那么这个类一定要实现接口中定义的所有的方法(默认为抽象方法)
  34. Java中接口不能实例化一个对象,我们可以利用它的实现类去产生一个对象,然后将这个对象的引用传递给接口定义的变量
  35. 接口中可以有数据成员,这些成员默认都是public static final
  36. Java中不允许类的多继承,但允许接口的多继承,一个接口可以继承自另一个接口
  37. 在Java中,一个类可以实现多个接口,一个类在继承另外一个类的同时,可以实现多个接口,注意:一定要先继承类实现接口即extends在implents之前
  38. 在内部类中定义的成员变量和成员方法与外部类中定义的成员变量和成员方法是不会冲突的,因为在外部类中定义的内部类仍然是一个独立的类
  39. Java中内部类(Innerclass)经编译后的名字为:外部类名$内部类名.class
  40. 在Java中可以使用static{}的形式来声明一个静态的语句块,它与静态成员的运行机制相同,再编译的时候运行,例如:static{System.loadLibrary("hello");},此语句在编译的时候加载名为hello的动态库文件
  41. 在Java源程序中定义的内部类可以访问外部类中定义的私有成员以及其他成员变量和成员方法,即内部类对象可以随意的访问外部类的所有成员
  42. 在Java中用new关键字产生的对象,都是在堆内存中分配空间,而声明的引用变量则在栈内存分配空间,同时this指针中也保存了本类对象的引用
  43. 在Java的源程序中定义了一个内部类(Inner class)和一个外部类(Outer class),如果在内部类的实例方法中访问外部类的成员变量,则可以使用Outer.this.成员变量名的方式,而如果要访问内部类中定义的实例变量则可以通过使用this.成员变量名的方式
  44. 如果在Java的main方法中访问外部类中的内部类,则要使用外部类名.内部类名的形式,例如在main方法中定义如下语句:Outer.Inner inner=outer.getInner();,其中getInner()是外部类中定义的一个返回类型为Inner的实例方法,其形式为:Inner getInner() {retuern new Inner();}
  45. main方法中可以使用Outer.Inner inner=outer.new Inner()的形式直接产生一个内部类的对象(即产生的内部类对象必须与一个具体的外部类对象相关联),但前提是必须在产生内部类对象之前产生一个外部类对象,否则内部类对象不能使用外部类中定义的实例变量
  46. 当把Java的一个内部类放在外部类的实例方法中定义时,就限定了这个内部类的应用范围,它的应用范围只能在这个方法中使用
  47. 我们可以把内部类的定义放在一个方法中,一个if语句中,一个语句块中,不管内部类定义所嵌套的层次有多深,他都可以访问外部类中的成员变量
  48. 如果在外部类的方法中定义一个内部类,且内部类需要访问方法中定义的局部变量方法的参数,则需要将方法中定义的局部变量和方法的参数都定义为final类型,即在方法中定义的内部类,如果要访问方法中定义的本地变量或方法的参数,则变量必须被声明final
  49. 内部类的访问修饰符可以为protectedprivate,而不限于public和friendly,还可以使用abstract,final和static修饰,当我们用static修饰内部类时,创建内部类对象的时候就不必先创建外部类对象,同时内部类也不能访问外部类中定义的非静态的成员
  50. 非静态的内部类不能定义静态的成员变量和成员方法,即内部类中不能有静态的声明
  51. 在Java中可以使用匿名的内部类,使用方法如下:比如我们创建了一个接口interface Animal {void eat();},接着又创建了一个类并在类中定义了一个方法,方法的返回类型为Animal类型例如:

class Zoo

{

Animal getAnimal()

{//注意接口类型不能实例化,只能用实现了接口的类去实例化

return new Animal(){//此处是通过创建匿名类实例化接口

public void eat(){}

};//此处即为匿名的内部类,注意不要丢掉分号

}我们在main方法中可以进行如下调用:

Zoo z=new Zoo();

Animal an=z.getAnimal();//此处我们建立了一个接口类型的对象

an.eat();

an.sleep();

以上就是我们对匿名类的调用

  1. 我们为什么使用内部类?

在内部类(inner class)中,可以随意的访问外部类的成员,但如果外部类要访问内部类的成员则需要先创建内部类的对象,通过内部类对象的引用来访问,而且内部类可以用于创建适配器类,适配器类是用于实现接口的类

54.例如我们有一个例子:

interface Machine

{

    void run();

}

class Person

{

    void run()

    {

        System.out.println("run");

    }

}

class Robot extends Person

{

    private class MachineHeart implements Machine//私有内部类

    {

        public void run()

        {

            System.out.println("heart run");

        }

    }

    Machine getMachine()

    {

        return new MachineHeart();

    }

}

class Test

{

    public static void main(String[] args)

    {

        Robot robot=new Robot();

        Machine m=robot.getMachine();

        m.run();

        robot.run();

    }

}

在此例中接口和person类中都定义了run方法,而Robot类为了继承person类和实现接口但又不引起编译错误,我们可以借助内部类来实现

55.匿名内部类用法的一个实例:

class A

{    void fn1()    {}

}

abstract class B

{    abstract void fn2();

}

class C extends A

{

    B getB()

    { return new B()//返回匿名内部类对象

        {    public void fn2()    {}

        };//此处创建了一个匿名类,注意不要漏掉分号

    }

}

class Test

{

    static void method1(A a)

    { a.fn1();    }

    static void method2(B b)

    { b.fn2();    }

    public static void main(String[] args)

    {    C c=new C();

        method1(c);//静态方法可以直接调用

        method2(c.getB());//静态方法可以直接调用

    }

}

56.Java中的异常抛给了调用者,即Java的异常向上一级抛出,在抛出异常的时候可以有多个捕获异常的语句即有多个catch语句,而且在捕获异常的时候将具体的异常放在前面而将通用的异常放在后面,也就是说catch语句的编写遵循由特殊到一般

57.当我们声明抛出异常时使用throws,而当我们抛出异常时使用throw

58.我们可以使用throws声明同时抛出多个异常

59.在Java的try/catch/finally语句中,无论是否发生异常finally语句都会执行,除非遇到终止程序运行的函数,此函数位于java.lang.System类中,函数形式为:static void exit(int status),其中参数status不为0时,程序终止运行,函数调用形式为:System.exit(-1);,当捕获语句try中含有此函数时,finally语句将中止执行

60. 对于RuntimeException,通常不需要我们去捕获,这类异常由Java运行系统自动抛出并自动处理

61. 如果父类中的方法抛出多个异常,则子类中的覆盖方法要么抛出相同的异常,要么抛出异常的子类,但不能抛出新的异常(注:构造方法除外)

62.我们可以在方法声明时,声明一个不会抛出的异常,Java编译器就会强迫方法的使用者对异常进行处理。这种方式通常应用于abstract base classinterface

63. 针对String"+"和"+=",是Java中唯一被重载的操作符;在Java中,不允许程序员重载操作符

64.String与StringBuffer的区别:String用于处理不变字符串而StringBuffer用于处理可变字符串,对于String类型可以通过"+"运算符来连接各种类型的数据,而对于StringBuffer类型只能通过append()函数来实现各种类型数据的连接

65.在Java的字符串中,索引的地址是从0开始计算的,即索引的范围为0~str.length()-1,当需要从StringBuffer类定义的字符串中调用delete(int start,int end)函数删除一些字符时,索引的地址范围为[start,end)

66.凡是用new分配的内存空间都是在堆内存中分配,而定义的引用变量则是在栈内存中分配一个引用空间,其中存放的是通过new关键字分配的内存的首地址(起初存放的是null),即把对象的引用送到引用变量

67. 在Java中,传参时,都是以传值的方式进行,对于基本数据类型,传递的是数据的拷贝;对于引用类型(数组、类等),传递的引用的拷贝

68.当使用System.out.println(对象名)方法打印一个对象时,会自动调用对象的toString()方法,返回关于该对象的一个字符串的描述,默认情况下toString()方法返回的字符串为对象所属的类名@对象名在内存中的地址的十六进制哈希码(例如:Point@19d643),当然toString()方法也可以重写,按照自己的需求输出

69. 对象的克隆:为了获取对象的一份拷贝,我们可以利用Object类的clone()方法(方法的返回类型为Object),通过在派生类中覆盖基类的clone()方法,并将方法声明为public,同时在派生类的clone()方法中,调用super.clone(),并在Object派生类中实现Cloneable接口

70. 在Java中,所有的数组都有一个缺省的属性length,用于获取数组中元素的个数, 数组的复制System.arraycopy(原数组名,原数组起始地址,目的数组名,目的数组起始地址,复制数组的长度),这个arraycopy()方法是个静态的方法,所以可以由类名直接调用(具体使用可以见Java API文档),当利用System.arraycopy()方法实现对象数组的复制时,传递的是对象的引用对象的地址,所以复制完后对数组的修改会对原数组有影响,例如:

Point[] pts1=new Point[]{new Point(1,1),new Point(2,2),new Point(3,3)};//复制时会将对象的地址传给pts2

Point[] pts2=new Point[3];

System.arraycopy(pts1, 0, pts2, 0, pts1.length);

for(int i=0;i<pts2.length;i++)

{

System.out.println("x="+pts2[i].x+",y="+pts2[i].y);

}

pts2[1].x=5;

pts2[1].y=5;

System.out.println("x="+pts1[1].x+",y="+pts1[1].y);//此条语句会输出改变后的数组值,因为pts2复制的是3个对象的地址

71. 数组的排序Arrays.sort(),而Collections.sort()方法则主要用于对列表的排序它位于java.util包中,此方法位于java.util包中的Arrays类中,所以在使用时应该先用import语句将其导入,而且sort()方法也是静态方法,并且该方法是对数组本身进行修改(具体使用见JavaAPI),例如:Arrays.sort(数组名);但是当利用此方法实现对象数组的排序时,数组中的所有元素必须实现Comparable接口(该接口在java.lang包中)而且元素之间可以比较,并且该接口中还有一个compareTo()方法,该方法的形式为:public int compareTo(Object o)(其返回类型为:int,而且结果只能为:正数,0,负数),因此当使用该方法实现对象数组的排序时,该对象的类必须实现Comparable接口,并且在类中重写compareTo()方法,但compareTo()方法中必须返回一个int型的数值

72. 在已排序的数组中查找某个元素:Arrays.binarySearch(),此方法位于java.util包的Arrays类中,所以在使用时应该先用import语句将其导入,而且binarySearch()方法也是静态方法(具体使用见Java API)

73.Java中为八种基本数据类型定义了相应的引用类型称为封装类(包装类),位于java.lang包

基本数据类型

封装类

boolean 

Boolean 

byte 

Byte 

short 

Short 

int 

Integer 

long 

Long 

char 

Character 

float 

Float 

double 

Double 

注意:封装类是一个只读类,一旦创建一个封装类对象,其内容是不能更改的,也就是说,针对每一个基本数据类型,我们都只能建立一个只读的封装类对象,它的值是不能修改

74.在java.lang包中有个Class类(注意与关键字class相区别),并且在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息,Class类没有公有的构造函数Class类的实例代表了在Java运行程序中的类和接口

75. 获取Class实例三种方式

    (1)利用对象调用getClass()方法获取该对象的Class实例,其中getClass()方法是在基类Object中定义的方法,因此所有类中都有getClass()方法,调用格式如下:Class c=对像名.getClass();

    (2)使用Class类的静态方法forName(),用类的名字获取一个Class实例;其中方法的参数为String类型,调用格式如下:Class c=Class.forName("类名");

     (3)运用.class的方式来获取Class实例,调用格式如下:Class c=public修饰的类名.class;,对于基本数据类型的封装类,还可以采用.TYPE来获取相对应的基本数据类型的Class实例,调用格式如下:Class c=Integer.TYPE(int.class); 而所有这些方式的输出都可以采用System.out.println(c.getName());

76.在Java中静态代码段的定义格式如下:

class 类名{

Static{

代码段的语句

}

77. 在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象,也就是说在Java程序运行的时候并不是加载所有的类,它只加载含有main()方法的类,当需要创建某个类的对象的时候,才会加载这个类的.class文件

78.对于Class类有一个newInstance()方法,这个方法的好处在于:在我们不知道这个类的名字的时候去创建这个类的对象(实例),但须注意一点:newInstance() 调用类中缺省的构造方法,调用格式如下:

if(args.length!=1){

return;

}

try{//由于forName()函数在运行时抛出异常,因而需要我们去捕捉异常

Class c=Class.forName(args[0]);//通过在运行时传递参数

Point pt=(Point)c.newInstance();

}

catch(Exception e){

e.printStackTrace();

}如果类中没有缺省的构造方法,那我们如何解决呢?方法是:采用反射API(映像API),去调用带参数的构造函数,来创建一个对象,那么什么是反射API呢?(可以通过查看Java给我们提供的指南文件夹中的reflect文件夹),反射API是通过java.lang.reflect这个包提供给我们的,下面我们给出一个反射API的应用:

if(args.length!=1){

return;

}

try{

Class c=Class.forName(args[0]);

Constructor[] cons=c.getDeclaredConstructors();//Constructor位于java.lang.reflect包中

for(int i=0;i<cons.length;i++){

System.out.println(cons[i]);

}

Method[] ms=c.getDeclaredMethods();//Method位于java.lang.reflect包中

for(int i=0;i<ms.length;i++)

{

System.out.println(ms[i]);

}//我们可以查看java.lang,reflect包中的方法了解

}

catch(Exception e){

e.printStackTrace();

}通过反射API我们可以动态的加载一个类,动态的创建一个实例,动态的调用一个方法

79.我们也可以使用java.lang.reflect包的Constructor类中的newInstance()方法来调用带参数的构造函数,该函数的形式为:Object newInstance(Object[] initargs),它需要一个对象数组作为参数,利用参数信息构建一个对象数组传给函数,我们可以通过使用getParameterTypes()方法获得构造函数的参数信息,它返回一个Class对象的数组,表示了我们的参数类型

80. 每一个Java程序都有一个Runtime类的单一实例,这个类位于java.lang包中,它没有public类的构造方法,我们可以通过Runtime.getRuntime()这个静态方法获取Runtime类的实例,Runtime对象有一个好处:我们可以通过exec()这个方法来执行一个外部的程序,例如:

Runtime rt=Runtime.getRuntime();

System.out.println(rt.freeMemory());

System.out.println(rt.totalMemory());

try{

rt.exec("notepad");//由于exec()方法会抛出异常因此需要我们去捕捉异常

//rt.exec("javac Main,java");这个方法可用于GUI上设计编译按钮

}

catch(Exception e){

e.printStackTrace();

}

81. Runtime类是使用单例模式的一个例子,那么什么是单例模式呢?单例(Singleton)模式:(1)一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,(2)单例类的一个最重要的特点是类的构造方法是私有的,从而避免了外部利用构造方法直接创建多个实例,下面是一个单例类的一个例子:

class Singleton//此处的类名可以随意

{    private static final Singleton st=new Singleton();

     private Singleton(){}

     public static Singleton getInstance()

     {     return st;

     }

}对于单例类最重要的就是这个类只能创建一个实例,即它的构造方法是私有的(private修饰的),例如Runtime类

82. Java是第一个语言级提供了对多线程程序设计的支持,实现多线程程序的两种方式:(1)从Thread类(位于java.lang包中)继承,一个Thread对象就代表了一个线程,例如我们的main()方法也是在一个线程中被运行的,也就是说当我们的java虚拟机启动的时候就有一个线程去执行main()方法,我们可以使用Thread的类的currentThread()方法获取当前执行main()方法的线程,然后使用getName()方法得到线程名,其中currentThread()方法的形式为:static Thread currentThread(),当我们想创建一个线程时就应该定义一个类,它继承了Thread类,而且还得重写公有的run()方法,在这个方法中我们可以打印出我们想要输出的信息,在main()方法中通过创建子类对象去调用start()方法,它会自动调用run()方法,例如:

public static void main(String[] args) {

MyThread mt=new MyThread();

mt.start();//start()方法可以自动调度线程的run()方法执行System.out.println("main:"+Thread.currentThread().getName()); }

class MyThread extends Thread{

public void run(){

System.out.println(getName());}}

注意:如果JVM中没有非后台的线程在运行,JVM就会退出了,main()方法是一个非后台线程,我们可以使用setDaemon()方法将一个线程设为后台线程,调用方式如下:mt.setDaemon(true);,在java中每一个线程都有一个优先级,我们可以通过Thread类的intgetPriority()方法获得线程的优先级,也可以通过setPriority(int newPriority)方法设置优先级,java为线程的优先级定义了三个常量(即参数newPriority可以取值为):(static int)MAX_PRIORITY=10,MIN_PRIORITY=1,NORM_PRIORITY=5,调用格式如下:mt.setPriority(Thread.MAX_PRIORITY);在java中如果一个线程拥有最高优先级,则不管有无yield()语句,此线程都一直在执行

(2)实现Runnable接口(该接口中只有一个run()方法),并且在实现了这个接口的类中重写run()方法,调用格式如下:

MyThread mt=new MyThread();

new Thread(mt).start();//此时会调用一个线程

83. Java线程调度器支持不同优先级线程的抢先方式,但其本身不支持相同优先级线程的时间片轮换,Java运行时系统所在的操作系统(例如:Windows2000)支持时间片的轮换,则线程调度器就支持相同优先级线程的时间片轮换

84. Java中同步有两种方式:同步块同步方法,他们都是通过synchronized关键字来实现的,同步块的调用方式为:

Object obj=new Object();

synchronized(obj){进入临界区的语句块}//obj对象可以为任意类型的任意对象或者是this,每当一个线程执行到synchronized(obj)时都会检查obj对象是否被加锁,如果没有则将其加锁然后进入直到退出synchronized(obj)这个方法,而其他线程在检查obj时发现被锁则被阻在门外,而同步方法的编写则采用如下格式:

public synchronized void 方法名() {进入临界区的语句块},而将此方法的调用放到实现了Runnable接口的类的run()方法中,同步方法利用的是this对象的锁,每个class也有一个锁,是这个class所对应的Class对象的锁(静态方法就是使用Class对象的锁),每一个对象都有一个监视器,或者叫做锁

注意:java中有两个方法容易引起死锁:suspend()和resume(),所以不建议使用

85. 每一个对象除了有一个锁之外,还有一个等待队列(wait set),当一个对象刚创建的时候,它的对待队列是空的,我们应该在当前线程锁住对象的锁后,去调用该对象的wait()方法,这意味着wait方法只能在同步方法或同步块中被调用,当调用对象的notify方法时,将从该对象的等待队列中删除一个任意选择的线程,这个线程将再次成为可运行的线程,当调用对象的notifyAll方法时,将从该对象的等待队列中删除所有等待的线程,这些线程将成为可运行的线程,wait和notify(位于Object类中)主要用于producer-consumer这种关系中,而且利用的是同一个对象

86.Java中线程的终止可以采用:设置一个flag变量和结合interrupt()方法

87. 所谓框架就是一个类库的集合,集合框架就是一个用来表示和操作集合统一的架构,包含了实现集合的接口与类

88. 集合框架中的接口:

Collection:集合层次中的根接口,JDK没有提供这个接口直接的实现类Set:不能包含重复的元素,SortedSet是一个按照升序排列元素的Set, List:是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式, Map:包含了key-value对,Map不能包含重复的key,SortedMap是一个按照升序排列key的Map

集合框架中的实现类(继承用实线,实现类用虚线):

 

89. ArrayList:我们可以将其看作是能够自动增长容量(是指当数组的容量不够时,他能自动开辟更大的空间并将原对象拷贝到新数组中)的数组,其在后台是通过对象数组实现的,利用ArrayList的toArray()可以返回一个对象数组,例如:

ArrayList al=new ArrayList();

Object[] obj=al.toArray();

利用Arrays.asList()可以返回一个大小固定的列表,此列表的大小不可以更改但其值可以更改,asList()函数是Arrays类(位于java.util包中)的静态函数,其返回类型为List型,asList()和toArray()可以看作是数组和集合框架的一个桥梁,asList()函数的调用如下:List l=Arrays.asList(obj);如果我们使用l.add("swss");将会出现错误,我们不可以更改大小而只能改变数组的元素,例如:l.set(int index,Object obj);

90. 迭代器(Iterator) 给我们提供了一种通用的方式来访问集合中的元素,是java.util包中的接口,我们可以认为迭代器指向两个元素中间的位置,调用格式如下:

ArrayList al=new ArrayList();

Iterator it=al.iterator();

while(it.hasNext()){

System.out.println(it.next());}

通用的方式来访问集合中的元素,例如:

public static void printElements(Collection c)

    {

        Iterator it=c.iterator();

        while(it.hasNext())

        {

            System.out.println(it.next());

        }

而在main()方法中可以调用printElements(al);

91.集合框架中的常用实现类一般都支持Iterator接口中的所有操作,但是集合框架中的接口不一定实现了remove()操作(该操作为可选操作)

92. Collections类:注意不要将这个类与Collection接口相混淆,Collections类中的所有方法都是静态的方法,该类提供了一些有用的功能,例如:排序:Collections.sort(),该方法主要用于对列表的排序(而我们前面介绍的Arrays.sort()主要用于数组的排序),而且调用该方法对列表中的元素进行排列时,所有元素必须实现Comparable接口(位于java.lang包中),并重写接口中的compareTo()方法,我们在对一个列表进行排序的时候,我们可以指定一个比较器,让列表根据我们指定的比较方法进行排序,比较器也是一个接口(位于java.util包中),其中有两个方法:int compare(Object o1,Object o2);Boolean equals(Object o);,比较器总是和一个指定的类相关联,我们还可以使用Collections.reverseOrder()作为比较器进行逆序排序,例如:

ArrayList al=new ArrayList();

Collections.sort(al,Collections.reverseOrder());

93. 取最大和最小的元素:Collections.max()、Collections.min(),在已排序的List中搜索指定的元素:Collectons.binarySearch(),调用此方法必须要保证List是已经排好序的

94. LinkedList(位于java.util包中)是采用双向循环链表实现的,利用LinkedList实现(stack)、队列(queue)、双向队列(double-ended queue )

95. ArrayList和LinkedList的比较:ArrayList底层采用数组完成,而LinkedList则是以一般的双向链表(double-linked list)完成,其内每个对象除了数据本身外,还有两个 引用,分别指向前一个元素和后一个元素,如果我们经常在List的开始处增加元素,或者在List中进行插入和删除操作,我们应该使用LinkedList,否则的话,使用ArrayList将更加快速

96. HashSet实现Set接口的hash table(哈希表),依靠HashMap来实现的,我们应该为要存放到散列表的各个对象定义hashCode()equals(),即在类中重写父类Object中的hashCode()和equals()方法,例如:

public int hashCode(){

return num*name.hashCode();//如果num能唯一表示一个学生,则可以直接使用num,否则要使用num*name.hashCode()

}

public boolean equals(Object o){

Student s=(Student)o;

return num==s.num&&name.equals(s.name);

}

注意==与equals()的区别从Object层次看,==和equals()的作用是相同的,都是比较内存地址,也就是说,都是比较两个引用是否指向同一个对象,是则返回true,否则返回false。

很多类都重写了equals()方法,最典型的是String类

97. 当散列表中的元素存放太满,就必须进行再散列,将产生一个新的散列表,所有元素存放到新的散列表中,原先的散列表将被删除。在Java语言中,通过负载因子(load factor)来决定何时对散列表进行再散列。例如:如果负载因子是0.75,当散列表中已经有75%的位置已经放满,那么将进行再散列。

HashSet类(位于java.util包中)缺省负载因子0.75,初始容量为16

98. TreeSet是依靠TreeMap来实现的,TreeSet是一个有序集合,TreeSet中元素将按照升序排列,缺省是按照自然顺序进行排列,意味着TreeSet中元素要实现Comparable接口,我们可以在构造TreeSet对象时,传递实现了Comparator接口的比较器对象

HashSet和TreeSet的比较:HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet

99.Map接口不同于Collection接口,它没有add()方法,只能通过Object put(Object key,Object value)方法向Map中添加元素,通过Object get(Object key)来获取元素,HashMap是实现了Map接口的实现类,它对key进行散列,我们可以使用Set keySet()方法返回一个Set类型的key视图,我们也可以使用Collection values()方法获得一个与key对应的值的视图,我们还可以使用entrySet()方法返回一个Set类型的键值对,而TreeMap是实现了SortedMap接口的实现类,它按照key进行排序,TreeMap方法与HashMap方法类似,我们可以参照HashMap方法

HashMap和TreeMap的比较:和Set类似,HashMap的速度通常都比TreeMap快,只有在需要排序的功能的时候,才使用TreeMap

100. Vector中的方法是同步的,如果我们不需要在多线程中实现同步,我们可以使用Collections类中的synchronizedList()方法代替Vector,我们要尽量用ArrayList代替Vector,Hashtable中的方法是同步的,如果我们不需要在多线程中实现同步Map,我们可以始终用HashMap代替Hashtable,如果需要同步我们可以使用Collections类中的synchronizedMap()方法实现同步Map

101. Properties(属性集)用来存储字符串形式的键-值对,我们可以使用System类中的static Properties getProperties()方法获得当前系统的属性,然后使用Properties类中的list()方法打印出这些属性,list(PrintStream out)方法需要一个PrintStream类型的参数,例如:

Properties pps=System.getProperties();

pps.list(System.out);

Properties可以读取配置文件,即扩展名为.ini的文件

102.在Java中与I/O操作相关的类是放在java.io包中,java.io包中的一个File类的对象,表示了磁盘上的文件或目录,File类提供了与平台无关的方法来对磁盘上的文件或目录进行操作,例如:

File f=new File("1.txt");

f.createNewFile();//这两条语句在当前目录创建了一个名为1.txt的文件

f.mkdir();//此条语句可以创建一个目录

File类的构造方法可以指定绝对路径,但在指定时由于java中"\"被认为是转义字符,所以在指定时需要使用"\\"的方式

File f=new File(File.separator);//我们用一个目录分隔符在我们当前的盘符下写一个目录分隔符,可以表示当前盘符的根目录(在Windows环境下我们可以使用分隔符代表我们当前程序所在磁盘的根目录),这表示在当前磁盘的根目录创建一个文件,其中separator是File类的String类型的静态常量,它表示一个分隔符,由于Java是跨平台的而在windows中目录是用"\"作为分隔符,而linux中是使用"/"作为分隔符,因此Java中提供了一个分隔符常量separator作为转换平台,例如:

File fDir=new File(File.separator);//代表磁盘根目录

String str="Exercise"+File.separator+"1.txt";

File f=new File(fDir,str);

f.createNewFile();

f.delete();我们也可以调用f.delete();上面新建的文件

注意f.deleteOnExit();与f.delete();的区别,f.deleteOnExit();是在程序结束后才将文件删除

在File类中有一个静态的方法createTempFile(前缀名,后缀名)用于创建一个临时文件,调用格式如下:

File f=File.createTempFile("winsun",".tmp");

它可以与f.deleteOnExit()结合使用,用于存放程序执行过程中产生的临时数据,然后在程序退出后,将这些临时文件删除

File类中有一个String[] List()方法,用于返回一个目录内下的子目录名和文件名,当然在List()方法中我们还可以定义过滤器,去显示指定类型的文件,例如:

File fDir=new File(File.separator);

String str="Exercise"+File.separator+"HelloWorld";

File f=new File(fDir,str);

String[] names=f.list();

String[] names=f.list(new FilenameFilter()

        { public boolean accept(File dir,String name)

            {    return name.indexOf(".java")!=-1;

            }

        });//此处定义了一个匿名内部类用于创建一个过滤器

103.Java中有两种基本的流是:输入流(Input Stream)输出流(Output Stream)。可从中读出一系列字节的对象称为输入流。而能向其中写入一系列字节的对象称为输出流,Java中流的分类:节点流:从特定的地方读写的流类,例如:磁盘或一块内存区域,过滤流:使用节点流作为输入或输出。过滤流是使用一个已经存在的输入流或输出流连接创建的

104. InputStream是一个抽象的类,三个基本的读方法

  • abstract int read() :读取一个字节数据,并返回读到的数据,如果返回-1,表示读到了输入流的末尾。
  • int read(byte[] b) :将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。
  • int read(byte[] b, int off, int len) :将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。off指定在数组b中存放数据的起始偏移位置;len指定读取的最大字节数

java.io包中 InputStream的类层次:

 

105. OutputStream是一个抽象的类,三个基本的读方法

  • abstract void write(int b) :往输出流中写入一个字节。
  • void write(byte[] b) :往输出流中写入数组b中的所有字节。
  • void write(byte[] b, int off, int len) :往输出流中写入数组b中从偏移量off开始的len个字节的数据

    java.io包中 OutputStream的类层次:

106. 基本的流类:

(1)FileInputStreamFileOutputStream节点流,用于从文件中读取或往文件中写入字节流。如果在构造FileOutputStream时,文件已经存在,则覆盖这个文件,调用格式如下:

FileOutputStream fos=new FileOutputStream("1.txt");

fos.write("hello world".getBytes());//getBytes()方法是String类中的一个方法,用于将字符串转换成字节数组,因为write()方法的参数为字节数组

fos.close();

FileInputStream fis=new FileInputStream("1.txt");

byte[] buf=new byte[100];

int len=fis.read(buf);

System.out.println(new String(buf,0,len));//println()方法不能输出字节数组,而需要通过String类的构造函数将字节数组转换为字符串输出

fis.close();

通过这两个流类我们就可以完成对文件读取和写入操作

(2)BufferedInputStreamBufferedOutputStream:过滤流,需要使用已经存在的节点流来构造,提供带缓冲的读写,提高了读写的效率,BufferedInputStream(InputStream in)和BufferedOutputStream(OutputStream out)

注意:使用这两个流类的时候,需要使用一个已经存在的节点输入流和节点输出流来构造,调用格式如下:

FileOutputStream fos=new FileOutputStream("1.txt");

BufferedOutputStream bos=new BufferedOutputStream(fos);

bos.write("hello world".getBytes());

bos.flush();//由于BufferedOutputStream类带有缓冲区,因此需要使用flush()函数强制送入文件

FileInputStream fis=new FileInputStream("1.txt");

BufferedInputStream bis=new BufferedInputStream(fis);

byte[] buf=new byte[100];

int len=bis.read(buf);

System.out.println(new String(buf,0,len));

bis.close();

(3) DataInputStreamDataOutputStream:过滤流,需要使用已经存在的节点流来构造,提供了读写Java中的基本数据类型的功能,调用格式如下:

FileOutputStream fos=new FileOutputStream("1.txt");

BufferedOutputStream bos=new BufferedOutputStream(fos);

DataOutputStream dos=new DataOutputStream(bos);

byte b=3;

int i=3;

float f=4.5f;

char c='a';

dos.writeByte(b);

dos.writeInt(i);

dos.writeFloat(f);

dos.writeChar(c);

dos.close();

FileInputStream fis=new FileInputStream("1.txt");

BufferedInputStream bis=new BufferedInputStream(fis);

DataInputStream dis=new DataInputStream(bis);

System.out.println(dis.readByte());

System.out.println(dis.readInt());//注意readInt()的顺序必须与writeInt(i)一致

System.out.println(dis.readFloat());

System.out.println(dis.readChar());

dis.close();

(4) PipedInputStreamPipedOutputStream:管道流,用于线程间的通信。一个线程的PipedInputStream对象从另一个线程的PipedOutputStream对象读取输入。要使管道流有用,必须同时构造管道输入流和管道输出流

107. Java的I/O库提供了一个称做链接的机制,可以将一个流与另一个流首尾相接,形成一个流管道的链接。这种机制实际上是一种被称为Decorator(装饰)设计模式的应用, 通过流的链接,可以动态的增加流的功能,而这种功能的增加是通过组合一些流的基本功能而动态获取的。例如:我们想将一些数据写入文件,我们应该创建FileOutputStream流类,如果我们希望流类带有缓冲区我们可以接着创建BufferedOutputStream流类,又如果我们希望将基本数据类型的数据写入文件我们应创建DataOutputStream流类,并将他们互相连接起来,如:

FileOutputStream fos=new FileOutputStream("1.txt");

BufferedOutputStream bos=new BufferedOutputStream(fos);

DataOutputStream dos=new DataOutputStream(bos);

面向字节的I/O流的链接:

Input Stream Chain

 

 

108. InputStream和OutputStream是针对字节流进行操作的,而Reader和Writer是针对字符流进行操作的即Reader和Writer这两个抽象类主要用来读写字符流,Java程序语言使用Unicode来表示字符串和字符,即使用两个字节(16位)来表示一个字符

109. java.io包中Reader的类层次:

 

其中InputStreamReader是一个实现从字节流到字符流转换的一个桥

java.io包中Writer的类层次:

 

其中OutputStreamWriter是一个实现从字符流到字节流转换的一个桥,通常我们很少直接使用OutputStreamWriter类输出信息,而是使用BufferedWriter类输出,例如:

FileOutputStream fos=new FileOutputStream("1.txt");

OutputStreamWriter osw=new OutputStreamWriter(fos);//我们通常很少使用OutputStreamWriter去直接进行写入操作

BufferedWriter bw=new BufferedWriter(osw);

bw.write("helloworld");

bw.close();

FileInputStream fis=new FileInputStream("1.txt");

InputStreamReader isr=new InputStreamReader(fis);

BufferedReader br=new BufferedReader(isr);

System.out.println(br.readLine());

br.close();

一个例子:

InputStreamReader isr=new InputStreamReader(System.in);//该构造函数的参数类型为InputStreamReader(InputStream in)

BufferedReader br=new BufferedReader(isr);

String strLine;

while((strLine=br.readLine())!=null){

System.out.println(strLine);

}

110.对字符集的操作实例:

Map m=Charset.availableCharsets();//位于java.nio.charset.*包中

Set s=m.keySet();

Iterator it=s.iterator();

while(it.hasNext()){

System.out.println(it.next());}

在Java中我们去构造字符和字符串称为解码,而将字符和字符串转换为字节称为编码,获得系统当前的字符集的实例如下:

Properties pps=System.getProperties();

pps.list(System.out);

我们也可以修改当前虚拟机的默认字符集,例如:

Properties pps=System.getProperties();

pps.put("file.encoding","ISO-8859-1");//此处将当前虚拟机的默认字符集改为ISO-8859-1,并为修改系统的字符集

int data;

byte[] buf=new byte[100];

int i=0;

while((data=System.in.read())!='q')// System.in.read()方法为从键盘读取数据,遵照系统中默认的编码方式读取,例如GBK

{buf[i]=(byte)data;//此处的字节数组采用GBK进行编码

i++;}

String str=new String(buf,0,i);//而将字节数组转换为字符串(即解码)时将采用ISO-8859-1进行解码

System.out.println(str);//此处将输出乱码

String strGBK=new String(str.getBytes("ISO-8859-1"),"GBK");//此处的str.getBytes("ISO-8859-1")是将字符串str按照ISO-8859-1编码会字节数组,其中getBytes(String encoding)是按照字符串型参数所指定的编码方式将字符串编码为字节数组

System.out.println(strGBK);

Java中使用Unicode字符集表示字符和字符串,在使用时一定要区分系统中的编码方式与虚拟机中的编码方式

111. RandomAccessFile类同时实现了DataInputDataOutput接口因而提供了对Java中基本数据类型的读写,而且提供了对文件随机存取的功能,利用这个类可以在文件的任何位置读取或写入数据,RandomAccessFile类提供了一个文件指针,用来标志要进行读写操作的下一数据的位置,我们在应用时一定要注意文件指针的位置,实例如下:

import java.io.*;

class RandomFileTest

{public static void main(String[] args) throws Exception

{Student s1=new Student(1,"zhangsan",98.5);

RandomAccessFile raf=new RandomAccessFile("student.txt","rw");

// RandomAccessFile中有两个参数,第一个用于指定存放的文件,第二个用于指定读写方式

s1.writeStudent(raf);

Student s=new Student();

raf.seek(0);//此处用于将文件的读写指针移到文件头,它的参数类型为long

for(long i=0;i<raf.length();i=raf.getFilePointer())

//RandomAccessFile类中有一个方法length()用于返回文件的长度,还有一个方法getFilePointer()用于返回文件指针的位置

{s.readStudent(raf);

System.out.println(s.num+":"+s.name+":"+s.score); }

raf.close();    }

}

class Student

{    int num;

    String name;

    double score;

    public Student()

    {    }

    public Student(int num,String name,double score)

    {    this.num=num;

        this.name=name;

        this.score=score;    }

public void writeStudent(RandomAccessFile raf) throws IOException

{raf.writeInt(num);

raf.writeUTF(name);// RandomAccessFile类中的writeUTF方法有一个好处,它在存放字符串的前面放置了字符串的长度

raf.writeDouble(score);    }

public void readStudent(RandomAccessFile raf) throws IOException

{    num=raf.readInt();

    name=raf.readUTF();//RandomAccessFile类中只有这一个方法可以读取字符串类型

    score=raf.readDouble();    }

}

112. 对象序列化:将对象转换为字节流保存起来,并在日后还原这个对象,这种机制叫做对象序列化。一个对象要想能够实现序列化,必须实现Serializable接口Externalizable接口,将一个对象保存到永久存储设备上称为持续性

Serializable接口没有任何方法,这是一个空接口,一个类只要实现了这个接口,这个类的对象就可以序列化

Externalizable接口是从Serializable接口中继承而来的

实现对象的序列化我们可以借助两个类:ObjectOutputStreamObjectInputStream,它们分别实现了DataOutput接口和DataInput接口,因而也增加了对Java基本数据类型的读写,它们中分别有writeObject(Object obj)方法和Object readObject()方法,用于将对象写入字节流中以及从字节流中将对象读出

注意:当我们去反序列化一个对象的时候,它不会去调用对象的任何构造方法

113. 当一个对象被序列化时,只保存对象的非静态成员变量不能保存任何的成员方法静态的成员变量,如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存,如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化,即我们可以使用transient这个标记将不可序列化的对象屏蔽起来,如果我们不想让某一个变量序列化,我们也可以使用transient将其隐藏起来。

注意:对象在进行序列化的时候将调用以下两个方法:

private void writeObject(java.io.ObjectOutputStream oos) throws IOException

    {    oos.writeInt(age);

        oos.writeUTF(name);

        System.out.println("Write Object");    }//这个方法可以实现数据的隐藏,例如没有输出salary这个值

private void readObject(java.io.ObjectInputStream ois) throws IOException

    {    age=ois.readInt();

        name=ois.readUTF();

        System.out.println("Read Object");    }

我们可以调用系统提供给我们的方法,也可以自己重写这个方法,虽然这两个方法是private修饰的,但它们是个特例,我们可以在类外使用

114. AWT(Abstract Window Toolkit),抽象窗口工具包,这是SUN公司提供的用于图形界面编程(GUI)的类库。基本的AWT库处理用户界面元素的方法是把这些元素的创建和行为委托给每个目标平台上(Windows、Unix、Macintosh等)的本地GUI工具进行处理。例如:如果我们使用AWT在一个Java窗口中放置一个按钮,那么实际上使用的是一个具有本地外观和感觉的按钮。这样,从理论上来说,我们所编写的图形界面程序能运行在任何平台上,做到了图形界面程序的跨平台运行

115. 容器里组件的位置和大小是由布局管理器来决定的。容器对布局管理器的特定实例保持一个引用。当容器需要定位一个组件时,它将调用布局管理器来完成。当决定一个组件的大小时,也是如此,在AWT中,给我们提供了五种布局管理器,他们分别是:

  • BorderLayout:这是框架默认的布局管理器,它包括东南西北中五个方位,分别用North,South,West,East,Center表示,不管我们怎样拖动窗口,我们只能改变中间的组件的大小,我们可以使用BorderLayout(int hgap,int vgap)来改变组件间的水平和垂直间距,例如:

Frame f=new Frame("MyFrame");

f.setLayout(new BorderLayout(10,10));

f. setLocationRelativeTo(null);//将窗口显示在屏幕中央

  • FlowLayout:这是一个流式管理器,并且是Panel组件的默认布局管理器,它依次摆放我们的组件,我们也可以使用如下格式来指定组件的对齐方式,例如:

f.setLayout(new FlowLayout(FlowLayout.LEFT));//左对齐

  • GridLayout:这是一个网格状的布局管理器,我们可以使用如下格式来设定网格的行列数以及网格之间的间隙

f.setLayout(new GridLayout(3,2,10,10));

  • CardLayout:使用该布局管理器在添加组件的时候,需要使用一个约束字符串,字符串的名字可以任意
  • GridBagLayout:这是最复杂的布局管理器,通常我们很少直接使用,我们可以使用集成开发环境为我们提供的拖放式组件

我们可以通过设置空布局管理器,来控制组件的大小和位置。调用setLayout(null),在设置空布局管理器后,必须对所有的组件调用setLocation()setSize()setBounds(),将它们定位在容器中

116.在Java中运行出来的框架窗口中的关闭按钮不能使用,如果我们想要使用,则需要在main()方法中注册窗口监听器,例如:

f.addWindowListenser(new MyWindowListenser());,而且还要定义一个MyWindowListenser类,该类继承了WindowAdapter适配器,或者实现了WindowListener接口。例如:

class MyWindowListenser extends WindowAdapter

{public void WindowClosing()

System.exit(0);

} }

我们还可以使用匿名内部类的方式,例如:

f.addWindowListenser(new WindowAdapter()

{public void WindowClosing()

{ System.exit(0);

} });//注意不要丢掉分号

117. JDK1.1的事件模型:委托模型

事件监听器:实现了监听器接口的类。一个监听器对象是一个实现了专门的监听器接口的类的实例

 

118.在Java中我们可以使用JOptionPane类中的showMessageDialog(Component parentComponent,Object message,String title,int messageType)方法为弹出式菜单项定义一个弹出式对话框,调用格式如下:

JOptionPane. showMessageDialog(null,"message");

如果想为一个框架添加一个弹出式菜单可以在框架的鼠标释放函数中使用如下格式:

final JFrame jf=new JFrame("Hello");

final JPopupMenu jpum=new JPopupMenu();

JMenuItem jmi1=new JMenuItem("Show");

jmi1.addActionListener(new ActionListener(){

    public void actionPerformed(ActionEvent e){

JOptionPane.showMessageDialog(jf, "I show to you");

}

});//注意分号不要漏掉

JMenuItem jmi2=new JMenuItem("Hide");

jpum.add(jmi1);

jpum.add(jmi2);

jf.addMouseListener(new MouseAdapter(){

    public void mouseReleased(MouseEvent e){

if(e.isPopupTrigger()){

         jpum.show(e.getComponent(), e.getX(), e.getY());

        }

    }

});

在Java中为框架添加菜单栏使用如下格式:

Frame f=new Frame("Hello");

MenuBar mb=new MenuBar();

f.setMenuBar(mb);

119. Applet又称为Java小应用程序,是能够嵌入到一个HTML页面中,并且可通过Web浏览器下载和执行的一种Java类, Applet不需要main()方法,由Web浏览器中内嵌的Java虚拟机调用执行, 通过限制applet在沙箱(applet的运行环境)中运行,保证了对本地系统而言applet是安全的

applet在沙箱中运行时:

⑴不能运行任何本地可执行程序

⑵除了存放下载的applet的服务器外,applet不能和其它主机进行通信

⑶不能对本地文件系统进行读写

120.由于Applet是从Panel类派生而来的,而Panel类默认的布局管理器是FlowLayout,因而Applet类的默认布局管理器也是FlowLayout

Applet的生命周期:

init():当浏览器加载applet,进行初始化的时候调用该方法

start():在init()方法之后调用。当用户从其它页面转到包含applet的页面时,该方法也被调用

stop():在用户离开包含applet的页面时被调用

destroy():当applet不再被使用,或浏览器退出的时候,该方法被调用

注意:JDK为我们提供的appletviewer这个工具它只认applet这个标签,其它代码会被忽略,因此我们可以在源文件中以注释的方式直接嵌入:

//<applet code=类名.class width=600 height=400>

//</applet>

并直接使用appletviewer 类名.java即可运行

121.在java.awt包中为我们提供了一个FontMetrics类,该类中提供了一些用于获得字符属性的方法,例如:getAscent()

122. 我们可以使用java.awt包中的Font类定义字体的格式,然后使用Graphics类中的方法对字体进行设置,例如:

public void paint(Graphics g)//此处只是声明了一个Graphics类的引用,只有当系统调用paint()方法时系统才会自动创建一个Graphics对象,然后将其传给g

{Font f=new Font("楷体_GB2312",Font.BOLD,30);//设置字体的格式

        //Font f=new Font(strFont,Font.BOLD,30);

        g.setFont(f);

        g.setColor(Color.blue);

        g.drawString("维新科学技术培训中心",0,30);

    }

字符的各种属性:

 

123. 正如应用程序可以通过命令行参数来获取信息一样,通过使用param标记,applet可以从网页中获取信息,例如:

<applet code="TestApplet.class" width=600 height=400>

<param name="font" value="DialogInput">

</applet>

我们可以在init()方法中调用String getParameter(String name)方法可以让applet从网页中获取信息,例如:

strFont=getParameter("font");

Font f=new Font(strFont,Font.BOLD,30);

124.我们可以使用public Graphics getGraphics()方法来获得一个Graphics对象,进而调用该类的方法,例如:

addMouseListener(new MouseAdapter()

{public void mousePressed(MouseEvent e)

{xOrigin=e.getX();

yOrigin=e.getY();}

public void mouseReleased(MouseEvent e)

{Graphics g=getGraphics();//该方法是Applet类从类 java.awt.Component 继承而来的方法

g.setColor(Color.red);

g.drawLine(xOrigin,yOrigin,e.getX(),e.getY());    }

});该方法可以在applet中划线段

125. Applet和浏览器的通信:

在浏览器中显示信息

调用AppletContext接口(该接口可以认为是Applet与浏览器之间进行交互的一个通信路径)中的showStatus()方法,该方法可以在浏览器的状态栏中显示信息,但是会被浏览器自身的信息覆盖,因此该方法不常用

请求浏览器显示指定的网页

调用AppletContext接口中void showDocument(URL url, String target)方法,该方法位于java.applet包中,例如:

try{

getAppletContext().showDocument(new URL("http://localhost/postinfo.html"),"_blank");    }//getAppletContext()方法可以得到一个AppletContext接口类型的对象

catch(Exception ex)

{ex.printStackTrace();}

126.Image img=getImage(getDocumentBase(),"T1.gif");//该方法可以得到一个Image对象,其中getDocumentBase()方法是将与网页文件在一起的图片文件对象赋给img,而getCodeBase()方法是将与类文件放在一起的图片文件对象赋给img

public void paint(Graphics g)

    { g.drawImage(img,0,0,this);}//该方法是将图片显示在网页中,其函数原型为:public abstract boolean drawImage(Image img,int x,int y,ImageObserver observer),其中ImageObserver(位于java.awt.image包中)是一个接口,该接口是一个可以得知图像状态信息是否改变的一个接口,java.awt.Container这个类已经实现了这个接口,因此Applet类(从java.awt.Container这个类间接派生)也间接实现了这个接口,所以我们可以直接将一个Applet对象作为参数传递给drawImage()这个方法

127.我们在调用repaint()时会产生一个名为AWT的线程,该线程会自动调用update()方法,update方法通常清除当前的显示并调用paint(),update()方法可以被修改,例如:为了减少闪烁可不清除显示而直接调用paint(),其中repaint()方法是java.awt.Component类中的方法

注意:更新显示由一种被称为AWT线程的独立的线程来完成

例如我们设计一个动画效果:

import java.applet.Applet;

import java.awt.HeadlessException;

import java.awt.*;

public class ShowImage extends Applet

{ private Image[] imgs;

private int totalImages=10;

private int currentImage;

private int i=0;

public void init()

{ imgs=new Image[totalImages];

for(int i=0;i<totalImages;i++)

{ imgs[i]=getImage(getDocumentBase(),"T"+(i+1)+".gif");

}

}

public void start()

{ currentImage=0;//因为每当画面更新的时候都会调用start()方法,所以能保证每次都从第一张图片显示

}

public void paint(Graphics g)

{ System.out.println("draw:"+i++);

g.drawImage(imgs[currentImage],0,0,this);

currentImage=++currentImage%10;//保证图片循环显示

try {

Thread.sleep(500);

}catch (Exception ex) {

ex.printStackTrace();

}

repaint();//调用该方法时会产生一个AWT线程,由该线程调用update()方法,接着update()方法再去调用paint()方法,完成画面的更新显示

} }

下面看一下一种改进的动画效果:(该程序的执行顺序为首先将图片读到(画到)内存缓冲区中,然后将内存缓冲区中的图片画到屏幕上,即每次都是先调用gBuf.drawImage()方法再调用g.drawImage()方法,但前提是需要用Component类中的createImage()方法创建一个内存缓冲区对象,接着在调用该对象的getGraphics()方法创建一个屏幕外的图像上下文)

import java.applet.Applet;

import java.awt.HeadlessException;

import java.awt.*;

public class DoubleBuffer extends Applet

{ private Image[] imgs;

private int totalImages=10;

private int currentImage;

private int i=0;

private Image imgBuf;

private Graphics gBuf;

private MediaTracker mt;//这是一个媒体跟踪器类,位于java.awt包中

public void init()

{

mt=new MediaTracker(this);//构造方法的原型为public MediaTracker(Component comp)

imgBuf=createImage(600,400);//该语句是在屏幕外(不是在屏幕上绘制)即内存中开辟一块空间存放图像,createImage()方法是java.awt.Component类中的方法,该方法的参数与网页中的Applet的大小相同

gBuf=imgBuf.getGraphics();//此处获得了Graphics对象gBuf,该对象与内存缓冲区相挂钩,即gBuf可以对缓冲区中的图像进行操作,此处的getGraphics()方法(位于Image类中)不同于Applet类中的getGraphics()方法

gBuf.setColor(Color.white);

gBuf.fillRect(0,0,600,400);//主要是在缓冲区中画出一个白色的填充矩形,用这个白色的填充矩形去填充这个绘图区域,这主要是为屏幕外画出图像做好准备

imgs=new Image[totalImages];

for(int i=0;i<totalImages;i++)

{ imgs[i]=getImage(getDocumentBase(),"T"+(i+1)+".gif");

mt.addImage(imgs[i],i);//该方法的原型为:public void addImage(Image image, int id)这个方法的id号不必是唯一的

}

try {

mt.waitForID(0);//此处是等待加载第一幅图像的ID号

}

catch (Exception ex) {

ex.printStackTrace();

}

}

public void start()

{ currentImage=0;

gBuf.drawImage(imgs[currentImage],0,0,this);//此处是将第一幅图片读到内存中的缓冲区中

currentImage=1;

}

public void paint(Graphics g)

{g.drawImage(imgBuf,0,0,this);//此处是画屏幕外的图像,将内存缓冲区中的图像在屏幕上显示出来,其中Graphics类的对象g与屏幕相挂钩,即将图像在屏幕上显示

if(mt.checkID(currentImage,true))

{ gBuf.fillRect(0, 0, 600, 400);//填充矩形区域相当于擦除了背景

gBuf.drawImage(imgs[currentImage], 0, 0, this);//此处是将下一幅图像送到内存缓冲区中,这里的gBuf是屏幕外的图形上下文,实际上是画到内存缓冲区中了

currentImage = ++currentImage % 10;

}

try {

Thread.sleep(500);

}

catch (Exception ex) {

ex.printStackTrace();

}

repaint();

}

public void update(Graphics g)

{ paint(g);

}

}

128.在java.awt包中有一个MediaTracker类,使用该类的mt.waitForID(int id)方法可以缩短等待播放的时间,还可以使用mt.checkID(currentImage,true)方法来判断一幅图像是否已经加载

129.我们可以在java中播放音乐,例如:

package imagetest;

import java.applet.*;

import java.awt.HeadlessException;

public class AudioTest extends Applet

{private AudioClip ac;

public void init()

{ac=getAudioClip(getDocumentBase(),"1.au");

}

public void start()

{ ac.loop();//此方法是AudioClip接口中的方法

}

public void stop()

{ac.stop();

}

}

130. applet的HTML标记和属性:

用于定位的applet属性

⑴width和height:必要的属性,以象素为单位,设定applet的宽度和高度。

⑵align:可选的属性,指定applet对齐的方式。

left:把applet放在网页左边,后面的文本会移至applet的右边。

right:把applet放在网页右边,后面的文本会移至applet的左边。

bottom:把applet的底部与当前行文本底部对齐。

top:把applet的顶部与当前行顶部对齐。

texttop:把applet的顶部与当前行文本顶部对齐。

middle:把applet的中部与当前行基线对齐。

absmiddle:把applet的中部与当前行中部对齐。

baseline:把applet的底部与当前行基线对齐。

absbottom:把applet的底部与当前行底部对齐。

⑶vspace和hspace:可选的属性,指定在applet上/下的象素数目(vspace)和applet两边的象素数目(hspace)。

用于编码的applet属性

⑴code:指定applet类文件的名字。该名字要么是相对于codebase,那么是相对于当前页面。

⑵codebase:可选的属性,告诉浏览器到哪个目录下去寻找类文件。

⑶archive:可选的属性,列出Java存档文件、包含类文件的文件或者applet需要的其它资源。

⑷object:用来指定applet类文件的另外一个方法。

⑸name:可选的属性,页面脚本编写人员希望赋予applet名字属性,这样,在编写脚本的时候,就可以用为该属性指定的名字来代表这个applet

131. Java语言中引入了套接字编程模型,在java.net包中为我们提供了ServerSocketSocket这两个类,分别用于服务器端编程和客户机端编程,在服务器端可以使用ServerSocket类的Socket accept()方法接受客户机端发来的连接请求,在Java中IP地址是通过InetAddress这个类来表示的,这个类为我们提供了很多的方法,例如:static InetAddress getByName(String host)这个方法可以返回指定主机的IP地址,static InetAddress getLocalHost()这个方法可以返回本地的IP地址,我们可以利用Socket这个类的getOutputStream()getInputStream()方法获得输出流输入流,用于向网络中写入数据和从网络中读出数据,当会话结束时我们可以使用Socket类的close()方法关闭连接

基于TCP的socket编程:

 

服务器程序编写:

①调用ServerSocket(int port)这个构造方法创建一个服务器端套接字,并绑定到指定端口上;②调用服务器端套接字socket的accept(),监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字,用这个套接字和我们客户端通信。对于服务器端的套接字来说,他不能直接用来数据交换,我们可以把这个服务器端的套接字看成监听套接字,它负责监听客户端请求,一旦有请求到来,那么accept()就接收请求,然后返回一个用于数据通信的套接字。③调用Socket类的getOutputStream()getInputStream()获取输出流和输入流,开始网络数据的发送和接收。④最后关闭通信套接字。例如:

public static void server(){

        try{

            ServerSocket ss=new ServerSocket(6000);

            Socket s=ss.accept();

            OutputStream os=s.getOutputStream();

            InputStream is=s.getInputStream();

            byte[] buf=new byte[100];

            int len=is.read(buf);

            System.out.println(new String(buf,0,len));

            os.write("Hello,Welcom you".getBytes());

            is.close();

            os.close();

            s.close();

            ss.close();

        }catch(Exception e){

            e.printStackTrace();

        }

    }

客户端程序编写:

①调用Socket(InetAddress address, int port)构造方法指定一个IP地址(在Java中IP地址是用InetAddress这个类来表示)和一个端口号创建一个流套接字(我们可以把凡是基于TCP创建的套接字称为流套接字),并连接到服务器端; ②调用Socket类的getOutputStream()getInputStream()获取输出流和输入流,开始网络数据的发送和接收。 ③最后关闭通信套接字。例如:

public static void client(){

        try{

            Socket s=new Socket(InetAddress.getByName(null),6000);

            OutputStream os=s.getOutputStream();

            InputStream is=s.getInputStream();

            os.write("Hi,This is Wangwu".getBytes());

            byte[] buf=new byte[100];

            int len=is.read(buf);

            System.out.println(new String(buf,0,len));

            is.close();

            os.close();

            s.close();

        }catch(Exception e){

            e.printStackTrace();

        }

    }

基于UDP的socket编程:

我们可以使用DatagramSocket类的构造方法DatagramSocket(int port)创建一个数据报套接字,之后可以利用DatagramSocket类的void receive(DatagramPacket p)和voidsend(DatagramPacket p)方法接受和发送数据,对于接收者来说我们可以调用DatagramPacket(byte[] buf,int length)这个方法来接收一个数据报,而对于发送者来说我们可以调用DatagramPacket(byte[] buf,int length,InetAddress address,int port)这个方法来发送一个数据报,然后调用send()方法把这个数据报发送出去

 

接收端程序编写:

①调用DatagramSocket(int port)创建一个数据报套接字,并绑定到指定端口上;②调用DatagramPacket(byte[] buf, int length),建立一个字节数组以接收UDP包 。③调用DatagramSocket类的receive(),接收UDP包,我们可以使用DatagramPacket类中的getLength()方法返回接收到的字符个数。④最后关闭数据报套接字。注意:接收端也可以给发送端发送信息,我们可以调用DatagramPacket类中的getAddress()方法得到发送方的IP地址,利用getPort()方法得到发送方进程所获得的系统为其分配的端口号,例如:

public static void receive(){

    try{

        DatagramSocket ds=new DatagramSocket(6000);

        byte[] buf=new byte[100];

        DatagramPacket dp=new DatagramPacket(buf,100);

        ds.receive(dp);

        System.out.println(new String(buf,0,dp.getLength()));

        String str="Hello,Welcome you";

        DatagramPacket dps=new DatagramPacket(str.getBytes(),str.length(),dp.getAddress(),dp.getPort());

        ds.send(dps);

        ds.close();

    }catch(Exception e){

        e.printStackTrace();

    }     

}

发送端程序编写:

①调用DatagramSocket()创建一个数据报套接字(发送端不需要指定端口); ②调用DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port),建立要发送的UDP包。 ③调用DatagramSocket类的send(),发送UDP包。④最后关闭数据报套接字。例如:

public static void send(){

    try{

        DatagramSocket ds=new DatagramSocket();

        String str="Hi,This is Zhangsan";

        DatagramPacket dp=new DatagramPacket(str.getBytes(),str.length(),InetAddress.getByName(null),6000);

        ds.send(dp);

        byte[] buf=new byte[100];

     DatagramPacket dpr=new DatagramPacket(buf,100);

     ds.receive(dpr);

     System.out.println(new String(buf,0,dpr.getLength()));

        ds.close();

    }catch(Exception e){

        e.printStackTrace();

    }

}

132.我们可以使用URL类URLConnection类实现一个简单的下载程序,例如:

public static void main(String[] args) {

         final String str;

JFrame jf=new JFrame("下载测试");

JPanel jp1=new JPanel();

JPanel jp2=new JPanel();

JPanel jp3=new JPanel();

JLabel jl=new JLabel("请输入访问地址:");

JTextField jtf=new JTextField(30);

str=jtf.getText();

final JTextArea jta=new JTextArea(15,40);

JButton jb=new JButton("Download");

jb.addActionListener(new ActionListener(){

    public void actionPerformed(ActionEvent e){

        try{

            URL url=new URL(str);

            URLConnection urlcon=url.openConnection();

            String line=System.getProperty("line.separator");//此处获得Java中换行符的表示

        jta.append("Host:"+url.getHost());

        jta.append(line);//添加换行符

        jta.append("Port:"+url.getDefaultPort());

        jta.append(line);

        jta.append("Content-Type:"+urlcon.getContentType());//获取内容的类型

        jta.append(line);

        jta.append("Content—Length"+urlcon.getContentLength());

        InputStream is=urlcon.getInputStream();

        InputStreamReader isr=new InputStreamReader(is);

        BufferedReader br=new BufferedReader(isr);

        String strLine;

        FileOutputStream fos=new FileOutputStream("1.html");

        while((strLine=br.readLine())!=null){

            fos.write(strLine.getBytes());//该方法在执行时不会将原文件的换行符写入

            fos.write(line.getBytes());//写入换行符

        }//其中getBytes()方法是将字符串转换为字符数组

        br.close();

        fos.close();

        }catch(Exception ex){

            ex.printStackTrace();

        }         

    }

});

jp1.add(jl);

jp1.add(jtf);

jp2.add(jb);

jp3.add(jta);

jf.getContentPane().add(jp1,"North");

jf.getContentPane().add(jp3,"Center");

jf.getContentPane().add(jp2,"South");

jf.addWindowListener(new WindowAdapter(){

    public void windowClosing(WindowEvent e){

        System.exit(0);

    }

});

//jf.setDefaultCloseOperation(EXIT_ON_CLOSE);

jf.pack();//将框架调整到适当大小

jf.setLocationRelativeTo(null);//将框架窗口显示在屏幕中间

jf.setVisible(true);//此处不能使用show()方法

    }

二进制的读取不能采用字符的方式去读取,而是采用字节流的方式读取,例如:

InputStream is=urlcon.getInputStream();

FileOutputStream fos=new FileOutputStream("1.html");

int data;

while((data=is.read())!=-1){

fos.write(data);

}

is.close();

fos.close();

133.在Java程序中,我们可以使用静态语句块实现加载动态库文件,即在类加载的时候加载库文件,使用方式如下:

static {

System.LoadLibrary("动态库文件名"); }

134. 图像文件的显示

Graphics类中确实提供了不少绘制图形的方法,但如果用它们在applet运行过程中实时地绘制一幅较复杂的图形(例如一条活泼可爱的小狗),就好比是在用斧头和木块去制造航天飞机。因此,对于复杂图形,大部分都事先用专用的绘图软件绘制好,或者是用其它截取图像的工具(如扫描仪、视效卡等)获取图像的数据信息,再将它们按一定的格式存入图像文件。applet运行时,只要找到图像文件存贮的位置,将它装载到内存里,然后在适当的时机将它显示在屏幕上就可以了。

1.图像文件的装载

Java目前所支持的图像文件格式只有两种,它们分别是GIF和JPEG格式(带有.GIF、.JPG、.JPEG后缀名的文件)。因此若是其它格式的图像文件,就先要将它们转换为这两种格式。能转换图像格式的软件有很多,如PhotoStyler等。

Applet类中提供了getImage( )方法用来将准备好的图像文件装载到applet中,但我们必须首先指明图像文件所存贮的位置。由于Java语言是面向网络应用的,因此文件的存贮位置并不局限于本地机器的磁盘目录,而大部分情况是直接存取网络中Web服务器上的图像文件,因而,Java采用URL(Universal Resource Location,统一资源定位器)来定位图像文件的网络位置。因此,Java专门提供了URL类来管理URL信息(关于该类的详细介绍见下一章)。

表示一个URL信息可分为两种形式:一种称为绝对URL形式,它指明了网络资源的全路径名。如:

绝对URL:"http://www.xyz.com/java/imgsample/images/m1.gif"

另一种称为相对URL形式,分别由基准URL(即base URL)再加上相对于基准URL下的相对URL这两部分组成,例如上面的例子可表示为:

基准URL:"http://www.xyz.com/java/imgsample/"

相对URL:"images/m1.gif"

现在,我们可以来看一下getImage( )方法的调用格式了:

Image getImage(URL url)

Image getImage(URL url, String name)

我们可以发现,这两种调用格式的返回值都是Image对象。确实,Java特别提供了java.awt.Image类来管理与图像文件有关的信息,因此执行与图像文件有关的操作时不要忘了import这个类。getImage( )方法的第一种调用格式只需一个URL对象作为参数,这便是绝对URL。而后一种格式则带有两个参数,第一个参数给出的URL对象是基准URL,第二个参数是字符串类型,它描述了相对于基准URL下的路径和文件名信息,因此这两个参数的内容综合在一起就构成了一个绝对URL。例如,下面两种写法所返回的结果是一样的:

Image img=getImage(new URL( "http://www.xyz.com/java/imgsample/images/m1.gif ");

Image img=getImage(new URL( "http://www.xyz.com/java/imgsample/ "), "images/m1.gif ");

表面看来,好象第一种调用格式较方便一些,但实际上第二种调用格式用得更普遍,因为这种格式更具灵活性。原来,Applet类中提供了两个方法来帮助我们方便地获取基准 URL对象,它们的调用格式如下:

URL getDocumentBase( )

URL getCodeBase( )

其中getDocumentBase( )方法返回的基准URL对象代表了包含该applet的HTML文件所处的目录,例如该文件存贮在 "http://www.xyz.com/java/imgsample/m1.html "中,则该方法就返回 "http://www.xyz.com/java/imgsample/ "路径。而getCodeBase( )方法返回的基准URL对象代表了该applet文件(.class文件)所处的目录。它是根据HTML文件的 "APPLET "标记中的CODEBASE属性值计算出来的,若该属性没有设置,则同样返回该HTML文件所处的目录。

好了,现在我们应该可以感受到基准URL的灵活性了吧。只要我们写下语句:

Image img = getImage(getDocumentBase( ), "images/m1.gif ");

那么即使整个imgsample目录移到别处任何地方,也可以正确装载图像文件,而采用对于绝对URL形式则需要重新修改applet代码并重新编译。

2.图像文件的显示

getImage( )方法仅仅是将图像文件从网络上装载进来,交由Image对象管理。那我们怎样把得到的Image对象中的图像显示在屏幕上呢?这又要回到我们的老朋友Graphics类中来了,因为Graphics类提供了一个drawImage( )方法,它能完成将Image对象中的图像显示在屏幕的特定位置上,就象显示文本一样方便。drawImage( )方法的调用格式如下:

boolean drawImage(Image img, int x, int y, ImageObserver observer)

其中img参数就是要显示的Image对象。x和y参数是该图像左上角的坐标值。observer参数则是一个ImageObserver接口(interface),它用来跟踪图像文件装载是否已经完成的情况,通常我们都将该参数置为this,即传递本对象的引用去实现这个接口。

除了将图像文件照原样输出以外,drawImage( )方法的另外一种调用格式还能指定图

像显示的区域大小:

boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)

这种格式比第一种格式多了两个参数width和height,即表示图像显示的宽度和高度。若实际图像的宽度和高度与这两个参数值不一样时,Java系统会自动将它进行缩放,以适合我们所定的矩形区域。

有时,我们为了不使图像因缩放而变形失真,可以将原图的宽和高均按相同的比例进行缩小或放大。那么怎样知道原图的大小呢?只需调用Image类中的两个方法就可以分别得到原图的宽度和高度。它们的调用格式如下:

int getWidth(ImageObserver observer)

int getHeight(ImageObserver observer)

同drawImage( )方法一样,我们通常用this作为observer的参数值。

下面的程序段给出了一个显示图像文件的例子,其显示结果如图4-14所示。

import java.awt.Graphics;

import java.awt.Image;

public class Images extends java.applet.Applet{

Image img;

public void init(){

img=getImage(getCodeBase(), "man.gif ");

}

public void paint(Graphics g){

int w=img.getWidth(this);

int h=img.getHeight(this);

g.drawImage(img,20,10,this); //原图

g.drawImage(img,200,10,w/2,h/2,this); //缩小一半

g.drawImage(img,20,200,w*2,h/3,this); //宽扁图

g.drawImage(img,350,10,w/2,h*2,this); //瘦高图

}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值