Java高级学习笔记

  Java高级部分

异常

l什么是异常(Exception)

Java程序在编译或运行时发生的错误,叫做异常。

 

l为什么进行异常处理

程序一旦发生异常,会中断指令正常的运行。为了保证程序在发生异常时,能够继续向下执行,所以,引入了异常处理机制。

 

l异常的类型

JVM异常

Java标准异常类:JDK提供的异常类。

自定义异常

 

l异常的层次结构

Throwable类:Throwable 类是 Java 语言中所有错误或异常的超类

Error

用于定义不指望程序能从其恢复过来的灾难性故障。Java中,对这种错误,不进行处理。

Exception类:指一些可以被捕获且可能恢复的异常情况。

编译时异常(checked)

»在编译时,必须进行处理;如果不处理,则无法通过编译。

»除了RuntimeException类及其子类,其他类都是编译时异常类。

运行时异常(unchecked)

»Java编译器允许程序不对其进行处理,直接由运行时系统来处理。

»RuntimeException及其子类都是运行时异常类。

 

l常见的编译时异常和运行时异常

常见的编译时异常:

ClassNotFoundException

FileNotFoundException

IOException

ParseException

SQLException

InterruptedException

常见的运行时异常:

ArrayIndexOutOfBoundsException

StringIndexOutOfBoundsException

NullPointerException

ClassCastException

ArithmeticException

NumberFormatException

常见运行时异常案例:

//rt1- ArrayIndexOutOfBoundsException

        int[] arrs = {1, 2, 3};

        System.out.println(arrs[3]);

        //rt2- StringIndexOutOfBoundsException

        String s = "abc";

        System.out.println(s.charAt(3));

        //rt3:  NullPointerException    引用类型声明的变量值为null的时候,如果调用该类型的属性或方法,抛出该异常。

        String str = "abc";

        str = null;

        str.toString();

        //rt4:  ClassCastException  父子类转换的时候出现。

        Object obj = new Object();

        String str2 = (String)obj;

        //rt5:  ArithmeticException

        int c = 10 / 0;

        //rt6:  NumberFormatException

        String sss = "10a";

        int x = Integer.parseInt(sss);

常见编译时异常案例:

        //nrt1:  ClassNotFoundException

        Class.forName("java.lang.String1");

        //nrt2:  FileNotFoundException

        new FileInputStream("d:/hao.txt");

        //nrt3:  IOException

        FileInputStream fis = new FileInputStream("d:/hao.txt");

        fis.read();

        //nrt4:  ParseException

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy");

        sdf.parse("1990");

        //nrt5:  SQLException

        Connection con = DriverManager.getConnection("jack", "xxx", "yyy");

        //nrt6:  InterruptedException

        Thread.sleep(100);

 

 

 

lJava异常的处理机制(两种)

n第一种: try...catch...finally

try
{
    //
可能抛出异常的代码。一般某行语句抛出异常,相当于创建了一       个异常类对象。
}
catch(
异常类型  e)//一个try块后,可以有多个catch代码块。
{
  //
一旦try代码块中抛出异常,就会自动创建一个相应类型的异常对象,此时,该异常对象如果是catch代码块中,参数的类型,或是其子类,则就被该catch代码块捕获。执行该catch代码块中异常的处理内容。
}
catch(
异常类型  e)//如果有多个catch,则catch后的参数类型必须由小到大(子类在前,父类在后)
{}

finally     //finally
代码块可有可无
{
        //
无论try代码块中有无异常,都会执行该代码块。一般用于收尾工作。比如IO对象的关闭,JDBC中对象的关闭操作。
}

 

try...catch...finally异常处理结构。运行顺序的几种可能。

1- try代码块中没有抛出异常。

此种情况,try块中的语句全部执行, catch不会执行,finally代码块语句执行,异常处理外的语句执行。

2- try代码块中抛出异常,且能被catch捕获

此种情况,try抛出异常行前的语句执行,捕获到异常的catch语句执行,finally语句执行,异常处理外的语句执行。

3- try代码块中抛出异常,catch代码块无法捕获

此种情况,try抛出异常前的语句执行,finally语句执行。程序结束。

 

l异常处理各部分详解(了解)

•try代码块

–捕获异常的第一步是用try{…}选定捕获异常的范围,由try所限定的代码块中的语句在执行过程中可能会生成异常对象并抛出。

•catch代码块

–每个try代码块可以伴随一个或多个catch语句,用于处理try代码块中所生成的异常对象。catch语句只需要一个形式参数指明它所能够捕获的异常类型,这个类必须是Throwable的子类,运行时系统通过参数值把被抛出的异常对象传递给catch块。

–在catch块中是对异常对象进行处理的代码,与访问其它对象一样,可以访问一个异常对象的变量或调用它的方法。getMessage()是类Throwable所提供的方法,用来得到有关异常事件的信息,类Throwable还提供了方法printStackTrace( )用来跟踪异常事件发生时执行堆栈的内容

–捕获异常的顺序和不同catch语句的顺序有关,当捕获到一个异常时,剩下的catch语句就不再进行匹配。因此,在安排catch语句的顺序时,首先应该捕获最特殊的异常,然后再逐渐一般化,也就是一般先安排子类,再安排父类

•finally代码块

–捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。不论在try代码块中是否发生了异常事件,finally块中的语句都会被执行。

–不论try块中的代码是否抛出异常及异常是否被捕获,finally子句中的代码一定会被执行:

•如果try块中没有抛出任何异常,当try块中的代码执行结束后,finally中的代码将会被执行;

•如果try块中抛出了一个异常且该异常被catch正常捕获,那么try块中自抛出异常的代码之后的所有代码将会被跳过,程序接着执行与抛出异常类型匹配的catch子句中的代码,最后执行finally子句中的代码。

•如果try块中抛出了一个不能被任何catch子句捕获(匹配)的异常,try块中剩下的代码将会被跳过,程序接着执行finally子句中的代码,未被捕获的异常对象继续抛出,沿调用堆栈顺序传递

 

ltry…catch…finally关键字的组合情况

try.catch..

try….catch….finally….

try.finally.

 

lfinal, finally, finalize区别?

final—修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重写。

finally—再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入finally 块(如果有的话)简单来说就是finally后的代码块无论如何都会被执行除非程序退出(System.exit(0))。

finalize—方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize()方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。

 

n第二种异常处理方式-   throws声明异常

如果一个方法中有异常,且该方法不进行异常捕获,则需要将该方法中的异常声明出去,让使用该方法的地方处理异常。

throws声明异常的方式,比异常捕获消极,所以,将异常捕获叫做积极的异常处理方式;把throws声明异常叫做消极的异常处理方式。

throws注意点:

1-用在方法的后面,表示声明异常;该方法有异常且交给使用该方法的地方处理。

2-一个方法可以throws多个异常;多个异常之间由逗号分隔,且从小到大顺序排列。

3-如果方法中可能产生的异常是编译时异常,则必须要throws声明出去;如果方法中可能产生的异常是运行时异常,则可以不必处理,也可以声明出去。

–在方法中,如果有异常,经常使用throws声明异常。

lthrow关键字

用于手动抛出异常。若某方法中需要直接抛出某异常时,可使用throw语句实现。

首先要生成异常对象,且抛出的异常必须是Throwable或其子类的实例。

如果一个方法可能抛出多个必检异常,那么必须在方法的声明部分一一列出,多个异常间使用逗号进行分隔

 

throwsthrow关键字?

  throws用于声明异常,在方法的后面出现。

  throw手动抛出异常,一般在方法体中使用。

 

l自定义异常

–Java语言中允许用户定义自己的异常类,但自定义异常类必须是Throwable的直接子类或间接子类。实际使用中,自定义异常常继承Exception或RuntimeException。

•如果一个自定义异常类为Exception子类,则该自定义异常类为编译时异常类;如果自定义异常类为RuntimeException的子类,则该自定义异常类为运行时异常类。

–自定义的异常类,一般只要声明两个构造方法,一个是不用参数的,另一个以字符串为参数。作为构造方法参数的字符串应当反映异常的信息。

–用户定义的异常同样要用try--catch捕获,但必须由用户自己抛出 throw new MyException()。

l使用异常的注意点:

–1、对于运行时异常,如果不能预测它何时发生,程序可以不做处理,而是让Java虚拟机去处理它;如果可以预知它可能发生的地点和时间,则应该在程序中进行处理,而不应简单的把它交给运行时系统。

–2、在自定义异常类时,如果它所对应的异常事件通常总是在运行时产生的,而且不容易预测它将在何时、何处发生,则可以把它定义为运行时异常,否则应定义为非运行时异常。

–3、方法可以不对异常进行捕获而直接将其抛出,并在方法声明中说明,最好将它们留给方法的调用者进行处理,这样会增加程序的灵活性

–4、异常对象的实例化和其后续处理工作是非常消耗资源的,过度的使用异常会明显影响程序的执行速度。所以,在使用异常处理时应该仔细考虑,只对有必要的异常情况使用异常,而不可以将异常泛化。

集合

l常见数据结构

–线性表

•顺序线性表- 数组(一块连续的内存空间):查询快,增删插入慢

•链式线性表:查询慢,插入、删除快

–单向链表

–双向链表

–栈:LIFO=last in first out  

–队列:FIFO=first in first out

 

l什么是集合?

–就是一组存放对象的容器。当需要对多个对象统一管理时,就需要集合对象。

 

l数组与集合

–数组:

•查询块,增删慢。

•相同数据类型,且定长。

•数组元素既可以为基本数据类型,也可以为引用类型。

–集合

•集合可以为不同类型(一般为相同类型,或自动转换),且长度自增。

•集合中的元素只能为引用类型或者说Object及其子类。集合中不能有基本数据类型的元素。

 

l集合层次结构

Collection

List:有序的,可重复的,允许有多个null

ArrayList//Vector(不经常使用)

LinkedList

Set:无序,不重复的,只允许有一个null

HashSet

TreeSet

Map

TreeMap

HashMap / Hashtable

Properties

•对于有层级结构的类或接口的关系,查看API时,最好从子类开始查找,如果子类没有,则依次向父类查找。

 

lCollection接口

–Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set 和 List)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些collection。

 

lList接口

–是Collection接口的子接口,该List接口中的元素,是有序的,可重复的,可多个为null;且可以通过索引对List接口中的元素进行访问。

–List除了继承Collection中的方法,自己新增了与索引相关的方法。

–List特有的方法listIterator()方法。

•在迭代过程中,不能使用迭代对象和集合对象同时操作集合中的元素。否则会报ConcurrentModificationException。

•ListIterator对象中除了可以在迭代过程中,移除集合中的元素,还可以对集合进行添加,设置。还可以从下往上遍历循环集合中的元素。

•Iterator接口只能从上向下遍历集合中的元素,且在迭代过程中,仅可以移除集合中的元素。

 

lList接口实现类

ArrayList-可变数组

底层由数组实现,查询速度快,删插入速度慢。

ArrayList类是不同步的,如果需要同步,可以使用Collections.synchronizedList(ArrayList对象)方法,使其同步。

Vector-矢量(了解)

•底层也是由数组实现

•Vector类使用与ArrayList基本相同,以后使用ArrayList

Vector类是同步的类。

•Vector类扩展了含有element的各种方法。

LinkedList-链表

底层有双向循环链表实现。查询速度慢,删插入速度快。

LinkedList除了实现父接口中的方法,还扩展了自己的方法。比如在列表的开头及结尾 getremove insert 元素提供了统一的命名方法。

此实现不是同步的,想要实现同步,可以使用List list = Collections.synchronizedList(new LinkedList(...));

–Stack类

•JDK中提供了Stack类,但因为其是Vector类的子类,所以,不符合堆栈的特点。

•自定义Stack类,实现堆栈的功能。

作业:

–使用LinkedList实现队列。

 

注意: ArrayList类与Vector类异同(了解)

–相同

•他们都是Collection或List接口的具体实现类。

•他们都能存储任意类型的对象。但通常情况下,这些不同的对象都具有相同的父类或父接口。

•他们都不能存储基本数据类型(primitive);集合对象.add(1),可以添加成功,但内部相当于int->Integer->Object.

•他们底层都是由可变数组实现的。容量都可以自动扩充。

–不同

•Vector是同步的,线程安全的,效率较低;ArrayList是不同步的,线程不安全,但效率高。

•Vector缺省情况下,自动增长为原来的一倍空间;ArrayList缺省情况下,自动增长为原来的一半。

 

–ArrayList, Vector, LinkedList真实开发中,如何选择使用?

–根据各自类的特点,按照实际需求,选择。

 

lIteratorListIterator接口

iterator()方法在Collection接口;

listIterator()方法在List接口中;

ListIterator接口中提供了比Iterator接口更多,更丰富的方法。

lIteratorEnumeration两个接口

Iterator迭代器取代了 Java Collections Framework 中的Enumeration。其功能相同。

迭代器与枚举有两点不同:

迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素。

方法名称得到了改进。

 

lSet接口

–没有扩展自己的方法,与Collection中的方法完全一致。

–HashSet

•此类实现 Set 接口,由哈希表(实际上是一个 HashMap实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。

•此实现不是同步的,想实现同步,需要使用Set s = Collections.synchronizedSet(new HashSet(...));

–TreeSet

•使用元素的自然顺序对元素进行排序,或者根据创建set 时提供的 Comparator 进行排序。

 

lSet接口实现类

nHashSet

此类不是同步的。

底层是由数组实现。且使用了哈希算法进行存储。

当一个对象存入HashSet集合对象中时,会先比较hashCode值,如果hashCode值不同,则直接存入集合;如果hashCode值相同,再使用equals方法比较对象,如果为true则不添加;如果为false,则添加到该集合对象中。也就是说,hashcode值相同,则equals未必相同,equals相同的两个对象,hashcode值一定相同

–原理:

•当将一个对象存放入HashSet集合对象中时,会根据hashCode%n,得到数组的下标位置。数组中,如果该位置已经有元素,则根据equals方法,比较两个对象。如果返回为true,则该对象不添加到数组中。如果返回为false,则该数组位置上的元素以链表的形式存放,新加入的对象放在链表头,最先加入的元素放在链表尾。如果数组中,该位置没有元素,则直接将元素放到该位置。

public class MySet

{

    private LinkedList[] lls = new LinkedList[10];

    public MySet(){}

    public boolean add(Object obj)

    {

        int hashcode = obj.hashCode();

        int index = hashcode % lls.length;

        //第一次向数组中存放元素。

        if(lls[index] == null)

        {

            lls[index] = new LinkedList();

            lls[index].addFirst(obj);

            return true;

        }

        else//从第二次赋值开始

        {

            //用obj对象使用equals方法与lls[index]表示的集合对象中每个元素比较属性值是否相同

            if(lls[index].contains(obj))

            {

                return false;

            }

            lls[index].addFirst(obj);

            return true;

        }

}

在使用HashSet时,如果添加对象后,对对象的属性值进行修改了,可能会造成内存泄漏。以后使用HashSet存放数据时,尽量不要修改其中对象的属性值。

如果自定义类的对象,存放入HashSet对象中,必须要实现hashCode()方法和equals方法。

nTreeSet类

–对集合中的元素,默认按照自然顺序自动排列。

–TreeSet底层使用是二叉树。

–对于TreeSet对象来说,要求存放入其元素的类型必须具有比较性。当类实现Comparable接口,并重写compareTo方法,该类的对象具有了比较性。把这种形式叫做按照自然顺序排序。

–TreeSet对象,通过compareTo对存放入其中的元素进行排序,且也是通过该方法比较两个对象是否相同。如果相同,则不会添加到TreeSet对象中。为了更精确的表示两个对象相等,当某个属性比较为0时,必须判断其他属性是否相等,当所有属性都相等时,两个对象真正相同。

 

如何判断集合中的元素与某个对象是否相同?

List体系:使用存放入该集合中对象所属类的equals方法

ArrayList/ Vector:底层可变数组, 查询快,删除和插入慢

LinkedList:底层链表, 查询慢,删除和插入快

Set体系:不重复且无序

HashSet:底层哈希表(本质上数组),增删改查都比较快。

         先判断hashcode值,hashcode相同,再使用equals判断。

TreeSet:底层二叉树,对存入其中的对象默认按照自然顺序排序

通过实现Comparable接口,并重写compareTo方法,此方法返回0,表示对象相同。

 

–Comparator比较器,实现对象的排序:

–创建一个类,实现Comparator接口,重写compare()方法。

–比较器主要用于指定对象排序的规则。

–以后,在实际排序中,最好类先实现一个Comparable,如果有其他排序规则的话,再使用Comparator.

 

lComparableComparator区别?

1-Comparable位于java.lang包中;Comparator位于java.util包中。

2-Comparable需要重写compareTo方法;Comparator需要重写compare方法。

3-Comparable只能对排序对象指定一个排序规则;Comparator,可以自己创建类,独立的定义对象的排序规则,而且可以定义多个排序规则。

 

Comparable相当于比较的对象自身具有比较特点;Comparator比较器相当于为集合对象设置比较规则。

 

•  作业:

–实现SMS系统,将学生的信息按照年龄排序(Comparable),打印输出。另外实现按照名字的长度排序(Comparator)

–分析:

使用Collections.sort(List对象);//按照自然顺序对List对象中的元素进行排序。Collections.sort(List对象,  Comparator  com);List对象按照比较器定义的规则进行排序。如果List对象中的元素实现自然顺序且又指定了比较器排序,此时比较器优先。

 

 

 

lMap接口

表示集合,与Collection体系无关,Map存放的是key-value的键值对。

在Map中,key不能重复,所以key的集合为Set;value的值可以重复,所以

value的集合可以看做Collection。

 

lMap内部结构示意图

lMap接口实现类

HashMap(JDK1.2)

–Hashtable(JDK1.0)

–   Properties

–TreeMap

•实现类用法及注意点:

–HashMap与Hashtable用法基本一致。Hashtable已经被HashMap取代。

–   HashMapHashtable

HashMap底层是哈希表,keyvalue可以为null;不同步,线程不安全,效率较高。如果想实现HashMap的同步,则使用Collections.synchronizedMap方法。

Hashtable底层是哈希表,keyvalue不能为null;同步,线程安全,效率较低。

•注意: HashSet底层其实就是使用HashMap

–   Properties用法

虽然Hashtable已经被HashMap取代,但Hashtable的子类Properties依然常用。

Properties属性列表中每个键及其对应值都是一个字符串

Properties pros = System.getProperties();得到JVM系统的属性。

–TreeMap(了解即可)

•底层是二叉树,对于放入该对象的键,默认按照自然顺序排列。TreeSet是由TreeMap实现的。

 

•注意:

–Map在真实使用中,key很少使用自定义的类型,如果为自定义的类型,则该类必须重写hashCode和equals方法。一般key都是使用八种基本数据类型对应的包装类或String类型。

–对于Map来说,key和value是一一对应的。如果出现一个key对应多个value的情况。比如巴塞罗那足球队有多个球员。将所有的球员添加到一个集合中,球队为key,集合为value。

 

•作业:

–1- 自定义一个类,将该类作Map对象的key。练习Map中的方法的使用

–2- 练习Hashtable方法。熟悉Hashtable的用法。

–3- 编写一个方法,该方法用于统计一个字符串中,每个字符的个数。

 

集合总结:

Collection体系

Map体系

 

泛型

•集合中不使用泛型和使用泛型的比较。

泛型在集合中使用的好处:当集合对象指定泛型类型后

1-类型安全(一旦集合对象确定泛型,只能存放入该类型的对象)

2-使用集合元素时,无需强制转换。

 注意:集合框架中,广泛的使用了泛型。掌握泛型在集合中使用。

•JDK1.5后,引入泛型;泛型就是对类型的参数化。主要用于解决类型安全问题。

案例:常用集合对象使用泛型案例(Map, Arraylist, Comparable, Comparator, Iterator...)

 

泛型类定义形式

        [public]  class 类名<T[, X, Y….]>{}

泛型类:类名

类型参数:T...

        ex:

泛型类的使用:T的具体值,在new的时候指定

–不指定泛型:类名对象名= new  类名();此时,T默认为Object

–如果指定T的值:类名<type>  对象名= new  类名<type>();

泛型类对象之间的赋值

–1- 不同的泛型类型之间,不能互相赋值。

–2- 类名  对象名  = new 类名<type>();正确

–3-通配符?。表示不知道将来传入的泛型是什么类型,使用?表示什么类型  都可以接收。

类名<?>  对象名  = new 类名<type>();正确

?可作为方法的参数。

 

•泛型接口定义形式:和类相似

public interface 接口名<T,  X...>{}

 

•继承关系中,实现类与泛型类;实现关系中,实现类与泛型接口?

不管是继承还是实现,如果实现类已经知道其父类或父接口中泛型是什么类型,则可以如下表示。

         public  A   extends  Father<String>{  }

         public  A   implements  IFather<String>{   }

 如果实现类不知道其父类或父接口中泛型是什么类型,则可以如下表示

          public  A<T>   extends  Father<T>{  }

       public  A<T>   implements  IFather<T>{   }

 

•泛型方法定义:

•将泛型定义在方法上,叫泛型方法。泛型方法的泛型仅仅对该方法起作用。

•定义格式:  泛型方法的标识,必须在方法的返回值前定义<T>泛型

[public …]  [修饰符]  <T>  返回值[T]  name(T 参数)

•注意点:

•在普通类或泛型类中都可以定义泛型方法。

•静态方法也可以定义泛型

•静态方法无法访问泛型类定义的泛型标识。因为静态先加载,而泛型类是new时指定泛型类型。

•如果静态方法方法在定义时,数据类型不确定,可以将其定义成泛型。

泛型上限和下限

泛型上限:  extends  T:表示可以接受T类型或者T的子类。 

泛型下限:  super    T:表示可以接受T类型或者T的父类。

 

lCollections类

–是一个工具类,类中所有的方法都是static。这些方法都是操作Collection体系的。

–Collections类中,主要对集合进行排序,查找,替换,填充等功能。

 

注意:

CollectionsCollection区别?

Collection是集合中的顶层接口,其右两个子接口List,Set.

Collections是工具类,里面提供了操作(Collection)集合对象的各种静态方法。

 

l集合和数组之间互相转换

数组->集合

通过Arrays.asList(T… obj)方法将数组转换成List对象。但是主要,转换后,不可以使用集合对象中的增删方法(使用的话,会报UnsupportedOperationException)。因为数组长度是固定的。但可以使用查询,判断等不修改数组长度的方法。

集合->数组

使用集合对象中的toArray()(该方法只能返回Object[])toArray(T[] t)可以指定具体数组元素的类型。

String[] objs2 = list.toArray(new String[10]);此时,T[]的数组的长度如果小于等于集合中元素的个数,JVM会自动以元素个数为数组分配内存。如果超过集合对象中元素的个数,剩余部分以数组元素类型默认值填充。

IO

lFile类

–File类表示文件或目录在JVM中的一种抽象形式。

–File类对文件或目录进行了封装,提供对文件或目录的操作方法,可以方便编程时使用。

–注意:

•创建File对象,File可以表示文件或目录。

•不同的OS中,路径的分隔符也不相同。windows中,路径分隔符可以使用/或\\;Linux或unix中,路径分隔符使用/;File类提供了一个静态的路径分隔符的属性,该属性根据JVM在不同的OS中,可以得到当前OS的路径分隔符。

–File类中方法讲解-参考API

函数的递归:

函数的递归调用,就是在函数中,调用自身。

1-函数递归,必须要有一个能够终止递归的出口。

2-函数递归时,还要注意,如果递归的层次过多,可能造成内存溢出

•作业:

–打印输出具有等级结构的目录信息

–比如:

 

lIO流操作

–Java程序操作存储设备中的数据(input,output)时,以流的形式传输的。

lIO流分类

IO流的分类:

IO流相对于JVM,根据传输方向划分

输入流

输出流

IO流根据流的传输数据类型

字节流(按字节传输)

字符流(按字符传输)

 

lIO流体系结构,均基于四个顶层的抽象类

对于字节流来说

InputStream:表示所有字节输入流的父类。

InputStream的子类,写法都是XxxInputStream.

OutputStream:表示所有字节输出流的父类。

OutputStream的子类,写法均是XxxOutputStream.

对于字符流来说

Reader:表示所有字符输入流的父类。

Reader的子类,写法都是XxxReader.

Writer:表示所有字符输出流的父类。

Writer的子类,写法都是XxxWriter.

节点流和包装流

节点流:流对象来说,能够直接操作数据源的。

包装流:以节点流或包装流作为构造方法的参数。无法直接访问数据源。

注意:所有跨出JVM区域的操作对象,必须要关闭。

 

l字节流体系结构

nInputStreamOutputStream体系

FileInputStreamFileOutputStream

节点流,可操作数据源文件

ByteArrayInputStreamByteArrayOutputStream

节点流,内存

BufferedInputStreamBufferedOutputStream

包装流,实现缓冲

DataInputStreamDataOutputStream

包装流,操作基本数据类型和String

ObjectInputStreamObjectOutputStream

包装流,操作基本数据类型和对象。

实现序列化和反序列化

PrintStream

打印流,节点流,包装流,自动刷新,方便打印输出println方法。

 

l字节流详解- 可以用于文本(较少),图片,音频,视频等操作。

InputStreamOutputStream

–InputStream

•1-对于输入流中的read方法,如果为接收键盘输入时,该方法是阻塞方法。当没有输入时,程序会停在read方法,等待外部输入。

•2-对于read方法读取文件,如果已到达文件末尾,则返回 -1。

•3-如果指定的读取文件不存在,则报FileNotFoundException异常。

–OutputStream

•1-从程序中向文件中写信息时,如果文件不存在,则会自动创建该文件,并将数据写入到文件中;如果文件已经存在,则会覆盖已存在的文件。(覆盖相当于将原文件删除,新建文件,写入数据)

•2-将数据写入到文件时,如果使用缓冲区,一般数据会暂时存放在缓冲区中。如果缓冲区已满,则自动将数据写入到文件中;如果缓冲区未满,则数据存放在缓冲区中。此时,必须手动的刷新缓冲区,将缓冲区中数据写入到文件中。

 

lFileInputStreamFileOutputStream

–属于节点流,此时数据源是文件系统中的文件。

–FileInputStream

•对文件内容读取。

•包含主要方法

–int  read():  一次读取一个字节,返回int类型

–int  read(byte[] bs): 一次读取多个字节,填充到bs中,返回的是真正填充到bs中的个数。

–void close():关闭对象

–int available():返回文件的字节数

–long skip(long n):忽略掉的字节数

–FileOutputStream

•创建该对象,如果指定的文件不存在,则会自动创建该文件;如果指定的文件存在,则根据构造方法中append的值,append为false,表示覆盖重名的文件。如果想在文件末尾追加,则手动设置为true即可。

•包含主要方法:

–void close()  关闭文件输出流对象。关闭前会刷新缓冲区

–void write(int c):一次写入一个字节

–void  write(byte[] bs):一次写入一个字节数组。

–void  write(byte[] bs,  int index,  int len):写出bs数组中一部分字节

–void flush():刷新缓冲区。

•作业:实现图片或音频的拷贝。

 

注意:

1-异常处理

直接使用try...catch...finally...处理

在方法中可以使用如下形式声明异常,且关闭对象。

public void show() throws  Exception

{

try

{

异常代码块
}

                         finally

                         {

                                 if(fis != null)

                                  {

                                          fis.close

}

}

}

2-带缓冲区和不带缓冲区运行速度的比较

lByteArrayInputStreamByteArrayOutputStream

–属于节点流,此时数据源是字节数组(缓冲区)。close无效。

–ByteArrayInputStream

•ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。

•关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。

–ByteArrayOutputStream

•此类实现了一个输出流,其中的数据被写入一个 byte数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()获得字节数组(缓冲区)内容和toString() 获取字节数组转换成字符串数据,size()获得缓冲区的大小。

•关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。

 

lBufferedInputStreamBufferedOutputStream

–属于包装流,实现缓冲功能。

–BufferedInputStream

•创建 BufferedInputStream 时,会创建一个内部缓冲区数组 ,提供缓冲功能。

–BufferedOutputStream

•该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。

                 对于过滤流来说,只需要关闭最外层的流对象即可。

lDataInputStream和DataOutputStream

–属于包装流,实现对Java基本数据类型和String进行读写操作。

–DataInputStream

•数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。

–DataOutputStream

•数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。

–注意:

DataInputStream读取必须与DataOutputStream写入的顺序相同。

l设计模式-装饰设计模式

比如Data,Buffered就是装饰模式。

装饰设计模式(Decorator):

           也叫做包装模式(Wrapper),装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

     使用装饰模式,客户端并不会觉得对象在装饰前和装饰后有什么区别,只是在不创建更多的子类模式下,将对象功能加以扩展。

装饰模式的构成:

1-抽象构建角色(Component)给出一个抽象的接口,以规范准备接受附加责任的对象。相当于IO流中的InputStream, OutputStream

2-具体构建角色(ConcreteComponent)定义一个将要接受附加责任的类。相当于IO流中的FileOutputStream和FileInputStream

3-装饰角色(Docorator)持有一个抽象构建(Component)角色的引用,并定义一个与抽象构件一致的接口,相当于IO流中FilterOutputStream, FilterInputStream

4-具体的装饰角色(ConcreteDecorator):负责给构建对象附加的责任。相当于IO流中的BufferedInputStream,DataOutputStream,ObjectInputStream...

ex

 

装饰模式特点:

1-装饰对象和真实对象具有相同的接口,这样客户端对象就可以以真实对象的相同的方式和装饰对象交互

2-装饰对象包含一个真实对象的引用(reference)

3-装饰对象接受所有来自客户端的请求,它把这些请求转发给真实对象

4-装饰对象可以在转发这些请求以前或者以后,增加一些附加的功能。这样能确保在运行时,不用修改给定对象结构就可以在外部增加附加功能。

注意:

在面向对象程序设计中,通常使用继承的关系来扩展给定类的功能。

比较继承扩展和装饰模式扩展。

ex:

 

装饰模式和继承区别?

a.装饰模式是对已经存在类进行的组合,比继承更加灵活。

b.装饰模式扩展的是对象的功能,不需要增加类的数量,而类继承扩展的是类的功能。

c.装饰模式不改变原类及原类的继承结构的情况下,动态扩展一个对象的功能。提高了扩展性。

d.装饰模式降低了类与类之间继承体系的臃肿,提高了扩展性。

 

lObjectInputStreamObjectOutputStream

属于包装流,实现基本数据类型和对象的读写操作。

这两个类是实现序列化和反序列化

序列化与反序列化

什么是序列化和反序列化

序列化:将内存中的对象保存到文件系统中。

反序列化:保存在文件系统中的对象读取到内存中。

如何实现序列化和反序列化

通过ObjectInputStreamObjectOutputStream类实现。

实现序列化的步骤:

1-序列化对象的类必须实现Seriablizable接口。

2-通过ObjectOutputStreamwriteObject方法实现。

实现反序列化的步骤

1-使用ObjectInputStreamreadObject方法。

2-要保证序列化和反序列化的类是同一个类,且seriaVersionUID要一致。

serialVersionUID的含义:

Java序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就 会出现序列化版本不一致的异常。(InvalidCastException)

serialVersionUID有两种显示的生成方式:

一个是默认的1L,比如:private static final long serialVersionUID = 1L;

一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final   long     serialVersionUID = xxxxL;

序列化与反序列化注意点:

1-基本数据类型可以直接序列化。

2-对于引用数据类型,要实现序列化,该类型必须实现Serializable接口。

3-静态变量不会被序列化。

4-如果序列化的对象所属的类中,包含了其他的引用类型作为其属性,则这些属性要么实现Serializable接口,要么忽略(transient)

–5-只有当父类也实现Serializable接口时,序列化子类对象时,父类才会被序列化(先有父类对象,才有子类对象),如果父类没有实现Seriablizable接口,则父类不会被序列化。当反序列化时,会调用父类无参的构造方法,所以,如果父类没有实现Seriablizable接口,则父类必须提供无参的构造方法。反序列化时候,不会调用子类的构造犯法,因为子类已经存在于文件中。

–作业:

每次退出系统,自动将添加的学生对象序列化到指定文件中;每次成功登陆系统后,反序列化。

 

lPrintStream类-打印流,只能向外输出。

–PrintStream,也是OutputStream的子类。既可以作为节点流,又可以作为包装流。主要作用能够方便地打印各种数据值表示形式。

•构造方法:

–PrintStream(File file)

–PrintStream(String fileName)

–PrintStream(OutputStream out)

PrintStream(OutputStream out, boolean autoFlush)

»如果atuoFlushtrue,则每当写入byte数组,调用println方法或写入换行符或字节(‘\n’),都会自动刷新缓冲区。

•常用方法:

–print(XXX)

–println(XXX)

常用字节流

总结:

 

l标准的输入,输出

–标准输出:

•System.out:表示将数据输出到控制台上。

•PrintStream ps = System.out使用PrintStream中的println,print等方法。

–标准输入:

•System.in:表示从键盘接收数据。

•InputStream is = System.in;  使用InputStream中的read方法,获得键盘输入的数据。

•重定向

//重定向标准输出。在此语句之后,使用System.out输出的数据,就转向了out.txt文件中

        System.setOut(new PrintStream("d:/out.txt"));

        System.out.println("hello world");

        //重定向标准输入。默认是从键盘获得输入.此语句之后,使用System.in获得数据,就是从hao.txt获取的

        System.setIn(new FileInputStream("d:/hao.txt"));

        InputStream is = System.in;

        int c = -1;

        while((c = is.read()) != -1)

        {

            System.out.println((char)c);

        }

 

l字符流

对于IO其实都是以字节流形式操作。在实际使用中,文本数据比较常用,为了方便操作文本数据,对字节流进行封装,形成了字符流。

注意:

        1- 字符流底层操作也是字节数据,只是对字节数据进行了处理。

2- 字符流对象中,含有码表。如果不指定,默认为JVM中的字符编码。

 

lReader和Writer体系

–FileReader和FileWriter

–BufferedReader和BufferedWriter

–InputStreamReader和OutputStreamWriter

–PrintWriter

 

lReader和Writer体系

–Reader类

•用于读取字符流的抽象类

–Writer

•用于写入字符流的抽象类

•含有写入字符串的方法:write(String str)

 

lFileReader和FileWriter类

–属于节点流,此时数据源是文件。

–与FileInputStream,FileOutputStream类似,不同之处在于一次读取一个字符。

–FileReader和FileWriter类中的方法,都是继承自其父类。

 

lBufferedReader和BufferedWriter

–属于过滤流,用于提供缓冲功能。

–BufferedReader

•除了读取的各个方法,还提供了readLine()方法,用于读取一行字符。

–BufferedWriter

•除了写入的各个方法,还提供了newLine()方法,用于换行。

•‘\r\n’用于换行,但不支持跨平台。newLine()方法,可跨平台。

 

lInputStreamReader和OutputStreamWriter

–转换流,可以在byte<->char之间互相转换;在转换的过程中,可以指定字符编码。

–InputStreamReader

•byte->char(解码)

–OutputStreamWriter

•char->byte(编码)

 

l编码-解码

常见的编码表:

ASCII:美国标准信息交换码;用一个字节的7位。

ISO-8859-1:欧洲码表。用一个字节的8位表示。

GB2312:中文编码表:约表示67千常用汉字

GBK:中文码表,表示更多中文文字。约2万多

GB18030

Unicode:万国码,所有文字用2个字节表示。Javachar型使用unicode编码。浪费空间。

UTF-8:用最多三个字节表示一个字符。(英文占一个字节,中文占23个字节)

编码-字符或字符串转换成字节数组

String->byte[]

1-str.getBytes();//按照默认字符集编码

2-str.getBytes(“utf-8”);//使用指定字符集编码

解码-字节数组转换成字符或字符串

byte[]->String

1-new String(byte[]);//按照默认字符集解码

2-new String(byte[], “utf8”);//按照指定字符集解码

乱码的产生,是因为编码和解码使用的码表不一致导致。只要编码和解码码表一致即可。

lPrintWriter

–提供方便打印字符的各种方法。可以当做节点流,也可以当做过滤流。

–与 PrintStream 类不同,如果启用了自动刷新,则只有在调用println、printf 或 format 的其中一个方法时才可能完成此操作,而不是每当正好输出换行符时才完成。

–构造方法:

•构造方法中可以有File, String, Writer(有刷新的重载构造方法),除此之外,还包含OutputStream(含有刷新的重载构造方法)(因为中间使用了OutputStreamWriter转换流)。

•println,print方法。

IO总结:

        如何使用IO流?

1-分析使用字节流还是字符流

图片、音频、视频等使用字节流

字符,文本使用字符流

2-操作哪个设备(文件, 内存,  网络...)

3-使用哪些流对象。

分析功能,需要使用哪些流对象。要求对各个流对象的特点熟练掌握。

 

lProperties

位于java.util包中,是Hashtable的子类。该类中存放键值对,且该类对象中的键值对都是String类型。

Properties常用来读取配置信息。当需要键值对及IO操作时,就可以考虑使用Properties

Properties类通过load方法,将配置文件信息加载到该类的对象中。可以通过Put,在对象中存储新的键值对;通过store方法,可以将新的键值对重写写入指定的文件中。

Properties config = new Properties();

        //将配置文件加载到Properties对象中。此时,load方法会将

        //配置文件中的key=value,保存成Properties对象的键值对

        config.load(new FileInputStream("d:/conf.txt"));

        config.list(System.out);

        Set<String> keys = config.stringPropertyNames();

        for(String key : keys)

        {

            System.out.println(key + "=" + config.getProperty(key));

        }

        //如果从内存中,将一个key=value保存到文件中

        config.setProperty("name", "root");

        config.setProperty("password", "root");

        config.setProperty("dburl", "com.mysql.url");

        config.store(new FileOutputStream("d:/db.conf"), "这是我的配置文件");

        //读取配置文件

        config.load(new FileInputStream("d:/count.txt"));

        String value = config.getProperty("count");

        if(value == null)

        {

            config.setProperty("count", 1 + "");

        }

        else

        {

            config.setProperty("count", Integer.parseInt(value) + 1 + "");

        }

        config.store(new FileOutputStream("d:/count.txt"), "login count");

 

作业:

SMS系统,通过Properties,实现登录信息次数的限制。如果输入用户名或密码错误超过三次,则提示错误信息。

 

lRandomAccessFile类

–RandomAccessFile类,不属于字节流或字符流的体系结构。它的父类是Object。因为该类可以读写文件内容,所以,它具备IO的特点,位于java.io包中。

–RandomAccessFile类,可以对文件进行随机的读取和写入操作。

–RandomAccessFile类随机访问文件的原理?

•随机访问文件的行为类似存储在文件系统中的一个大型byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。

•通常,如果此类中的所有读取例程在读取所需数量的字节之前已到达文件末尾,则抛出 EOFException(是一种 IOException)。如果由于某些原因无法读取任何字节,而不是在读取所需数量的字节之前已到达文件末尾,则抛出 IOException,而不是EOFException。需要特别指出的是,如果流已被关闭,则可能抛出 IOException。

–构造函数:

•RandomAccessFile(File file, String mode)

–mode 参数指定用以打开文件的访问模式。允许的值及其含意为 :

–"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出IOException。 

–"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 

–常用方法:

•read,write,操作的都是字节,与字节流中的方法完全一致。

•readXxx,writeXxx:用于操作基本数据类型和字符串。

•readLine:每次读取一行信息

•getFilePointer();返回long,表示文件指针的偏移量

•length();返回long,表示指向的文件的长度。

•seek(long pos) :表示设置文件指针的偏移量。

•int skipBytes(int n) :尝试跳过输入的 n 个字节以丢弃跳过的字节 ,返回跳过的字节数。只能向后跳过。

lIO流作业

    1. 创建程序从标准输入读入文本,在每行前加入行号后写入文件中。文件名由命令行参数指定。

标准输入:  System.in    键盘。

命令行参数: 

分析:

     使用字符流

     输入的设备:键盘;输出设备:文件。

  BufferedReader      readLine.

  new BufferedReader(new  InputStreamReader(System.in));

  BufferedWriter写入到文件中。

    1. 打印一个目录下的所有的文件,包括目录中的目录里包括的文件。
    2. 查找一个目录中文件名里所有包含abc字符的文件.
    3. 编写一个final类,从配置文件db.conf中读入各个配置参数,并提供读取配置参数的方法。配置文件格式如下(#开始的行表示注释):Properties直接加载即可。

#database type, such as sybase, oracle

DB_TYPE = sybase

#the host where db installed

DB_HOST = 192.168.0.100

#the port of db serves

DB_PORT = 6666

#usename and password of db access

USER_NAME = waytojob

PASSWORD = tomorrow

6. 试改进练习2(文件拷贝),每次读写2K字节,以加快处理速度。比较读写单个字节和读写2K字节的效率

7. 打开一个文本文件,每次读取一行内容。将每行作为一个String读入。按相反的顺序打印出的所有行

如在E盘下有一个Test.txt文本文件内容是

#4

#刘德华#89#77#60

#张信哲#76#97#70

#周杰伦#89#88#90

#随便啦#87#70#80

要求通过java读取文本并把文本中的每一行后面的三个成绩相加,

最后按从大到小的顺序输出到另一个文本文件中.

 

输出后的文本文件内容应为:

#4

#周杰伦#267

#张信哲#243

#随便啦#237

#刘德华#226

0s-1

1s-2

2s-3

I   s-1-i

9.  all.txt 是一个GBK编码的文件,要求写一个程序,将这个文件转换一下,改成utf-8编码的。改好后的效果是在utf-8编码的项目里打开可以直接阅读不乱码。

 

线程

l程序、进程、线程

程序:使用编程语言编写的有序的代码集合,静态。program

进程:运行的程序叫进程,动态。process

线程:线程是进程中一个独立的控制单元。也可以说,线程是进程中,一段代码的执行流。线程控制进程的执行。  thread

 

每个进程,至少要有一个线程。一个进程中,可以同时运行多个线程,即多线程。

 

l主线程

以前所学程序从main方法开始执行,即一个主线程在执行。

–获得主线程的名称:

•Thread.currentThread.getName();

l多线程同时运行

•线程的执行和调度

–线程只有获得CPU的使用权或时间片,该线程才能执行。

–线程优先级不同的,采用抢占式机制;优先级相同呢,分时调度。

lJava中创建线程的2种方式

1-第一种创建线程的方式:

1-自定义一个类,该类继承Thread类,并重写Thread类中的run方法。(该类叫线程类,run方法中为线程体)

2-创建该线程类的对象。

3-调用start方法,启动线程,并执行run方法。

2-第二种创建线程的方式:

1-自定义一个类,实现Runnable接口,并重写run方法。(该类不是线程类,仅定义了线程执行内容)

2-创建自定义类的对象

3-创建Thread(创建线程类对象),并指定线程运行的方法(自定义类的对象)

4-线程类对象调用start方法,启动线程并执行run方法中的线程体。

两种实现线程的比较

第二种方式线程对象与线程执行的代码分离,比较灵活。

Java中,类是单继承的。所以,第一种方式,继承一个类以后,不能再继承其他类了;而第二种方式,实现一个接口,还可以继承其他类。

建议使用第二种方式定义线程。

l线程中,调用start方法和调用run方法的区别?

假如说,在main方法中,通过线程类对象,调用start方法和run方法。当调用run方法时,相当于普通的方法调用,此时,run方法中的内容,是main线程执行的;如果调用的是start方法,此时main线程运行到start方法后,程序中,启动了一个新的线程(此时,至少存在两个线程,main和新线程)。之后,main继续向下执行,而同时启动的线程执行run方法中的代码。

l课堂练习

实现两个线程,一个打印*,一个打印#

l线程的状态和生命周期

Java中用Thread.State枚举表示线程的状态。

lThread类的方法

–start()

•启动线程并开始执行run 方法。

–run()

•线程执行代码存放在该方法中。

–sleep(long millis)

•在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

–join()

•停止当前正在执行的线程,直到加入的线程执行完毕后,再执行当前线程。

–yield()

•当前正在执行线程释放CPU,回到与其他线程同等机会竞争CPU使用权。

–Java中线程的优先级

•Java优先级只表示获得CPU的使用权机率增多。

•在java中,对线程的优先级进行查看和设置。getPriority();setPriority(int level)。

•不同的平台,优先级也不同。不利于跨平台。

•Thread中有三个常量,表示优先级

–MAX_PRIORITY

–MIN_PRIORITY

–NORM_PRIORITY

–用户线程和守护线程

•守护线程也叫精灵线程。Java中通过setDaemon(true)设置线程为守护线程。

•守护线程一般是在后台为其他线程提供服务的线程,它一般应该是一个独立的线程。守护线程中run方法内,是一个死循环。

•守护线程一般在其他线程启动前启动,当进程中只有守护线程时,进程结束。

用户线程和守护线程区别?

如果守护线程是进程中唯一运行的线程,则程序会自动退出。比如JVM中隐藏的垃圾收集线程。

l线程组

–每一个线程都属于一个线程组。在main方法中,创建的线程,如果不为其指定所属的组,默认属于main线程组。

–如何设置自定义的线程组

 

l作业:

–1- 实现一个线程类,能够拷贝文件。同时启动三个实例,拷贝3个文件。

–2- 多线程拷贝一个文件。RandomAccessFile。

 

l线程的互斥和同步(多线程安全问题)

1-  通过售票案例,可以知道,当多个线程访问共享数据时,可能会出现数据的不完整性和不正确性。

2- 如何解决?

        要保证多线程中共享数据的正确性;需要在某一时刻,仅让一个线程访问共享数据即可。

     3- 如何实现

使用同步锁机制,解决多线程共享数据的问题。

什么是同步锁机制

–synchronized关键字和对象的互斥锁,实现多线程访问共享数据,保证数据的完整性和正确性。

对象互斥锁:

–默认情况下,每个对象除了拥有属性和方法,还有一个互斥锁。该锁是对象的一部分。每个锁,还有一个锁池的概念。

•Object obj = new Object();obj对象就有一把互斥锁,且有锁池。

•String s = new String();s对象上也有一个互斥锁,且有锁池。

–互斥锁和synchronized一起使用,就可以保证某一时刻,只有一个线程访问共享数据。前提是,多个线程使用的是同一把互斥锁。

实现同步锁机制的两种形式

1-使用同步代码块

synchronized(互斥锁)

{

    //操作共享数据的代码

}

2-使用同步方法

public synchronized  void xxx()//使用同步方法时,默认的锁是this

{

    //操作共享数据的代码。

}

        注意:

1- 如果一个线程,即使获得CPU时间片,但没有获得锁,该线程也无法执行同步代码块中的代码。

2- 如果多个线程运行到同步代码块,此时,没有获得锁的线程会在锁池中等待。

3-  多个线程必须使用同一把锁,才能实现同步。

4-  synchronized同步是通过程序执行的效率换取共享数据一致性。所以,非常消耗资源。synchronized代码块不扩大范围。最好使用方法。

5-   synchronized关键字不能修饰构造方法和抽象方法。

6-   一个线程可以获得多个锁,但一个锁只能被一个线程得到。

7-   synchronized修饰的非静态方法,默认使用的锁是this对象;如果同步方法是静态方法时,比如下面静态方法位于Test.java类中。

public static synchronized void xxx(){},此时的锁是当前类.class对象,即Test.class对象(字节码文件加载到内存中对应的Class对象)

 

                 使用synchronized弊端:

                         虽然可以解决多线程共享数据的问题,但大大降低程序执行的效率

        以前知识重提:

                 StringBuilder,                                         StringBuffer

                 ArrayList,                                                  vector

                 不同步,线程不安全,效率较高   同步,线程安全的,效率低

l懒汉式单态模式

public class Singleton{

    private static Singleton instance = null;

    private Singleton(){}

    //如果使用懒汉式,延迟加载。

    /* 当多个线程执行该段代码时,线程A判断instance,此时为null,符合条件。线程A就进入到X处。如果此时A失去了CPU使用权。B线程又执行该段代码,且判断instance,也是为null。B也进入到X处。两个线程会分别创建一个对象。此时就不符合单态模式规则了。*/

    public static Singleton getInstance(){

        if(instance == null){

            //X

            //延迟到此处创建对象

            instance = new Singleton();

        }

        return instance;

}

     /*

     * 解决上述问题,可以在方法前面加上synchronzied关键字

     * 注意,此时的锁对象为Singleton.class的对象。

     * 使用同步,解决了多线程问题。如果多个线程执行到如下方法,

     * 执行效率会非常低。

     */

    public synchronized static Singleton getInstance1(){

        if(instance == null){

            instance = new Singleton();

        }

        return instance;

    }   

    /*  使用双重判断,提高执行效率。(画流程图表示)

     * 如下形式,可以提高多线程访问的速度。

     * 使用双重判断。A线程执行时当1处判断,第一次执行到此处,instance为null,此时,A进入到X处。此时A释放CPU,B线程在1处判断,进入到X位置。这个时候,X有两个线程。A获得同步块锁的时候,进入到同步代码块中。2处判断,判断是否已经创建了instance对象。 因为是第一次执行,instance为null。A就创建了instance对象。A释放锁后,B进入到同步块中。此时intance已经不为null。所以,仅仅创建了一个对象。

2处判断,主要用于多个线程第一次进入到1判断内部时,防止创建多个instance对象。

1处判断,当第一次instance对象创建完毕后,其他多个线程,不需要执行同步代码块了,此时会提高线程执行速度。

     */

    public static Singleton getInstance2() {

        //BCDEFG

        if(instance == null) {//1

            //X  B

            synchronized(Singleton.class){

                if(instance == null) {//2

                    instance = new Singleton();

                }

            }

        }

        return instance;

    }

}

 

l线程的状态和生命周期

l死锁

当两个线程各自拥有自己的锁对象,但彼此又希望获得对方的锁对象,此时两个线程互相等待,造成死锁。

–为什么会造成死锁?

•因为频繁的使用synchronized。通常是同步代码块中嵌套同步代码块

–如何解决?

•减少线程同步资源定义

public class LockThread implements Runnable{

    static ArrayList al1 = new ArrayList();

    static ArrayList al2 = new ArrayList();

    boolean flag ;

   

    public LockThread(boolean flag) {

        this.flag = flag;

    }

   

    @Override

    public void run(){

        if(flag){

            synchronized(al1) {

                System.out.println("获得al1对象的锁");

                try{

                    Thread.sleep(5000);

                }

                catch (InterruptedException e){

                    e.printStackTrace();

                }

                synchronized(al2){

                    System.out.println("在线程al1同步块中,获得al2对象的锁");

                }

            }

        } else{

            synchronized(al2){

                System.out.println("获得al2对象的锁");

                try{

                    Thread.sleep(5000);

                }

                catch (InterruptedException e) {

                    e.printStackTrace();

                }

                synchronized(al1) {

                    System.out.println("在线程al2同步块中,获得al1对象的锁");

                }

            }

        }

    }

}

Main

LockThread lock1 = new LockThread(false);

        LockThread lock2 = new LockThread(true);

        Thread th1 = new Thread(lock1, "A");

        Thread th2 = new Thread(lock2, "B");

        th1.start();

        th2.start();

 

l线程通信机制

线程间的通信,是通过Object类中的wait()和notify()/notifyAll()方法实现的。

wait和notify()/notifyAll()机制

1-  当synchronized方法中的wait方法被调用时,当前线程会释放该线程的所有的锁,并在等待池中等待。要从等待池中唤醒某一线程或所有等待线程,则其他线程必须调用同一个对象上的notify()/notifyAll()方法。

2-     一旦唤醒等待池中的线程,则这些被唤醒的线程进入锁池,等待获得锁。

3-     如果被唤醒的线程获得了锁,则线程进入可执行状态。获得时间片之后,该线程从wait之后的代码开始执行。

注意:

1-  wait()和notify()/notifyAll()方法必须在synchronized修饰的代码块或方法中。

        2-  每个Java对象,除了默认拥有属性和方法,还有锁,锁池,等待池。

线程间进行通信典型案例:生产者和消费者。

public class Factory{

    private int sum = 0;

    public synchronized void produce() {

        while(sum == 1)//仓库已经存满,此时生产线程在等待池中等待{

            try {

System.out.println(Thread.currentThread().getName() + "-------" + "将要进入等待池");

                this.wait();//生产线程运行到次方法,就到this对象的等待池中等待了。

System.out.println(Thread.currentThread().getName() + "-------" + "被唤醒,继续执行");

                if(sum == 1){

                    return;

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }       

        System.out.println(Thread.currentThread().getName() + "-------" + "生产产品");

        sum = 1;//生产产品。之后,通知等待池中等待的线程,过来消费

System.out.println(Thread.currentThread().getName() + "-------" + "唤醒等待池中的所有线程");

        this.notifyAll();//唤醒this等待池中所有等待的线程。

        System.out.println("生产线程唤醒后的代码****************************");

    }   

    public synchronized void consume() {

        while(sum == 0)//表示仓库空,让消费线程等待 {

            try{

System.out.println(Thread.currentThread().getName() + "-------" + "将要进入等待池");

                this.wait();

System.out.println(Thread.currentThread().getName() + "-------" + "被唤醒,继续执行");

                if(sum == 0) {

                    return;

                }

            }catch (InterruptedException e){

                e.printStackTrace();

            }

        }

        System.out.println(Thread.currentThread().getName() + "-------" + "消费产品");

        sum = 0;//消费产品,产品消费后,通知生产线程,生产产品

System.out.println(Thread.currentThread().getName() + "-------" + "唤醒等待池中的所有线程");

        this.notifyAll();//通知this等待池中的线程。

        System.out.println("消费线程唤醒后的代码~~~~~~~~~~~~~~~~~~~~~~~~~~~");

    }

}

public class Consumer implements Runnable{

    private Factory f = null;

    public Consumer(Factory f){

        this.f = f;

    }   

    @Override

    public void run() {

        while(true){

            try{

                Thread.sleep(1000);

            } catch (InterruptedException e){

                e.printStackTrace();

            }

            f.consume();

        }

    }

}

public class Producer implements Runnable{

    private Factory f = null;   

    public Producer(Factory f){

        this.f = f;

    }

    @Override

    public void run(){

        while(true){

            try{

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }           

            f.produce();

        }

}

}

l线程的状态和生命周期

l作业

–体会并实现通过继承Thread类创建线程,并实现下述输出结果:一个线程连续输出26个大写字母A-Z,另一个线程输出26个小写字母a-z;

–创建两个线程,一个每3秒打印出线程名和当前时间,另一个每1秒打印出线程名和当前时间

–创建两个线程的实例,分别将一个数组从小到大和从大到小排列.输出结果

–编写两个线程,共享数据StringBuffer。一个向StringBuffer添加数据,一个从StringBuffer读取数据,如果StringBuffer中没有数据则等待

GUI图形界面

l软件分类

–单机软件

•office;画图;PS;CAD

–分布式软件

•CS

–Client/Server:客户端/服务器

–QQ,MSN,SKYPE….

–下载客户端,安装。

–特点:

»升级维护,麻烦

»Server端承担大部分业务,客户端承担一部分业务。

»处理速度快。

•BS

–Browser/Server:浏览器/服务器

–163.com; sohu.com; qq.com

–只需要有一个浏览器

–特点:

»维护,升级简单。

»Server几乎承担所有的业务,浏览器几乎不承担压力。

»处理速度较慢。

lGUI

–Graphical   User   Interface

–AWT

•abstract window toolkit,抽象窗口工具集。

•重量级组件,调用底层系统的方法,显示不独立于平台。

•java.awt.*;     java.awt.event.*

–Swing

•轻量级组件,独立于平台,由Java语言开发。

•javax.swing.*;

–SWT

•eclipse

l组件类的层次结构

lSwing

–Swing是建立在AWT基础上的一种增强型的Java GUI组件(工具集,工具包),主要是使用轻量组件替代AWT中绝大多数的重量组件。界面组件的渲染完全由Java自身完成,而不是调用操作系统的界面组件实现,是由Java自己绘制完成的。

–这样做的好处是程序对组件的调用上完全Java化,较少掺合其它语言,有利于跨平台(即Swing界面在不同的平台上外观完全一样,真正做到了平台独立)。而AWT是调用操作系统的对等界面组件,因此在某些时候会受到平台的限制。

–Swing中的每一个轻量级组件必须出现在重量级容器中,所以Swing的小应用程序Japplet,窗体,窗口,对话框都必须是重量组件,以提供绘制Swing轻量级组件的窗口。

–Swing组件由40多个,一部分为AWT组件的替代品,另一部分是提供给用户开发图形用户界面增添的组件。

–Swing包括javax.swing包及其子包。

–Swing组件除了AbstractButton类之外,都以J开头

 

lSwing组件类的体系结构

l创建窗体的两种形式

–直接创建JFrame。

–继承JFrame,创建子类。

备注:讲解JFrame控件

l窗体显示在屏幕中间

 

l布局管理器-LayoutManager

–布局管理器用于布局容器中各种组件存放的形式。

–所有的布局都继承自LayoutManager接口

–常见的5种布局

•FlowLayout(流式布局)

•BorderLayout(边界布局)

•GridLayout(网格布局)

•CardLayout(卡片布局)

•GridBagLayout(网格包布局)

 

lBorderLayout

n边框布局管理器

u将整个容器的区域划分成5部分:分别为North北部;South南部;East东部;West西部;Center中部。

uWindow,JFrame,JDialog默认的布局管理器是BorderLayout

u备注:讲解JFrame,JDialog

lFlowLayout

–流式布局管理器:

•FlowLayout将添加到容器中的组件,按添加顺序从上到下,从左到右依次排列。

•FlowLayout是JPanel容器的默认布局管理器。

•组件在容器中排列时,组件和组件之间默认的间距是5px,默认的对齐方式居中对齐。

•容器调用setLayout方法,用来设置布局管理器。

备注:讲解JPanel,讲解设置布局管理器

lGridLayout

–网格包布局管理器

•网格布局管理器对象创建时,可以指定行和列。

•容器中每个组件占据一个网格,大小完全相同。

备注:讲解JButton

lCardLayout(卡片布局)

项目中讲解

 

l绝对定位

使用控件的setBounds方法。使用前,通过setLayout(null)

案例:登录界面,讲解JLabel,JTextField,JPassword

l事件处理机制

–包含三方面

•事件源:组件。

•事件:对组件的某一种操作,就会自动产生一个事件对象。

•事件处理者:当对组件进行操作时,会自动产生相应的事件对象,一旦产生该对象,就由相应的事件处理者处理该事件。一个组件可以委托多个事件处理者。

 

l事件处理机制编写步骤

–1-对于某种类型的事件XxxEvent,要想接收并处理这类事件,必须定义相应的事件监听器类,该类需要实现与该事件相对应的监听接口XxxListener.

–2-事件源通过addXxxListener(XxxListener对象)方法,将以后可能发生在自己身上的XxxEvent事件,委托给XxxListener对象。

事件处理案例:三种形式

1-本类实现监听

2-其他类实现监听

3-匿名内部类实现监听

MouseEvent->MouseListener->addMouseListener()

观察者设计模式。

 

作业:

l适配器类

–如果使用监听接口,必须实现接口中定义的所有方法,但大多数情况下只是需要使用其中的一个或几个方法。

–为了避免麻烦,AWT中提供了多个相应的适配器类,这些适配器类实现了响应接口的所有方法的空操作。

–当使用时只需要扩展适配器类并且覆盖我们需要实现的方法即可,而无需实现原来接口中所有的方法。

–注意:

•当接口中,仅有一个方法时,AWT中并不提供适配器类。

•所有的适配器类都是抽象类。

–注意:

•XxxEventàXxxListeneràaddXxxListeneràXxxAdapter类

lSwing组建讲解

l作业:

1- 注册界面

2- 实现记事本程序

3- 统计一个文件夹中所有.java文件的代码行数、不包括空行、注释行。如下图

4- 统计一个文件夹中所有.java文件中代码行,空行,注释行信息,按照图示显示。

JDBC

l什么是JDBC

JDBC是Java DataBase Connectivity的缩写,它是连接Java程序和数据库服务器的纽带。

 

JDBC的实现封装了与各种数据库服务器通信的细节。Java程序通过JDBC API来访问数据库。

Java程序通过JDBC API访问数据库

lJDBC组成

1:JDBC的实现

包含3部分

1-JDBC驱动管理器

java.sql.DriverManager类,由SUN公司实现,负责注册特定JDBC驱动器,以及根据特定驱动器建立与数据库的连接。

2-JDBC驱动器API

由SUN公司制定,java.sql.Driver接口是其最主要接口。当数据库供应商或者其他第三方工具提供商为特定数据库创建JDBC驱动器时,该驱动器必须实现JDBC驱动器API。即实现Driver接口。

3-JDBC驱动器

由数据库供应商或者其他第三方工具提供商创建(因为只有他们才最了解与特定数据库通信细节,有能力对特定数据库的驱动器进行优化),也称为JDBC驱动程序。JDBC驱动程序实现了JDBC驱动器API,负责与特定的数据库连接,以及处理通信细节,它可以注册到JDBC驱动管理器中。

        2:JDBC API:

                 Java程序通过JDBC API访问各种数据库。

JDBC的实现

如上图,JDBC驱动器才是真正的连接Java应用程序与特定数据库的纽带。Java影城程序如果希望访问某种数据库,必须先获得相应的JDBC驱动器的类库,然后再把它注册到JDBC驱动管理器中。

 

lJDBC驱动器分为4类

n第一类驱动器:JDBC-ODBC驱动器

ODBC(Open  DataBase Connectivity, 开放数据库互连)是微软公司为应用程序提供的访问任何一种数据库的标准API。JDBC-ODBC驱动器为Java程序与ODBC之间建立了桥梁,使得Java程序可以间接地访问ODBC API。

JDBC-ODBC驱动器是唯一由SUN公司实现的驱动器,属于JDK的一部分,在默认情况下,该驱动器已经在JDBC驱动管理器中注册了。

JDBC-ODBC连接数据库的速度比较慢,不提倡使用。

 

n第二类驱动器:由部分Java代码和部分本地代码组成,用于与数据库的客户端API通信。

使用该驱动器时,不仅需要安装相关的Java类库,还要安装一些与平台相关的本地代码。

 

n第三类驱动器:完全由Java语言编写的类库

它用一种与具体数据库服务器无关的协议将请求发送给服务器的特定组建,再由该组建按照特定数据库协议对请求进行翻译,并把翻译后的内容发送给数据库服务器。

 

n第四类驱动器:完全由Java语言编写的类库

它直接按照特定数据库的协议,把请求发送给服务器数据库。

        以上四种驱动器访问数据库的速度由快到慢,依次为4,3,2,1。大部分数据库供应商都为他们的数据库产品提供了第三类,第四类驱动器。

        Java应用程序应该优先考虑使用第三类和第四类驱动器,如果某数据库不存在,则把第一类和第二类驱动器作为暂时的替代品。

       

        注意:

                 Java程序必须通过JDBC驱动器访问数据库,因为DriverManager类,才实现Java程序与各种不同的JDBC驱动器通信。DriverManager类使用桥梁设计模式,成为连接Java应用程序和各种JDBC驱动器的桥梁。

            Java应用程序只和JDBC API打交道。JDBC API依赖DriverManager类来管理JDBC驱动器。如果程序向一个数据库提交一条SQL语句,DriverManger类就会委派对应的数据库的驱动器执行这个任务。

                

lJava程序使用JDBC访问数据库案例

查询

增删改

 

lJava程序使用JDBC连接数据库的步骤:

–1- 加载并注册数据库的驱动。

•Class.forName(“包名.类名”);

–2-建立与数据库的连接

•DriverManager.getConnection(url, user, password);

–url:连接数据库的地址

–user:数据库的登录名

–password:数据库的密码

–3-创建Statement/PreparedStatement/CallableStatement对象

•conn.createStatement();

–4-执行SQL语句,并返回执行的结果。

•4.1-如果是查询,使用stat.executeQuery(sql);,返回结果集

•4.2-增删改,stat.executeUpdate(sql);返回受影响的行数

–5-关闭相关对象。

•数据库连接非常消耗资源,一定要关闭。

 

lJDBC接口和类详解

nDriver接口

所有JDBC驱动器都必须实现Driver接口,JDBC驱动器由数据库厂商或者第三方提供。

 

nDriverManager类

DriverManger类用来建立和数据库的连接以及管理JDBC驱动器。

DriverManager类方法都是静态的:

        registerDriver(Driver driver):在DriverManager类中注册JDBC驱动器

        getConnection(url, user, pwd):建立和数据库的连接,并返回Connection对象

 

注意:

        JDBC-ODBC驱动器是JDK自带的,默认已经注册。有些驱动器的Driver类在被加载的时候,自动创建本身实例,然后调用DriverManager.registerDriver()方法注册自身。所以,在Java程序中,只要通过Class.forName()方法加载数据库驱动类即可,而不必注册。

 

nConnection接口

表示Java程序和数据库的连接。包含以下方法

getMetaDate():返回表示数据库的元数据的DatabaseMetaData对象。元数据包含了描述数据库的相关信息。

createStatement():创建并返回Statement对象

prepareStatement(String sql):创建并返回PreparedStatement对象

 

nStatement接口

提供了三个执行SQL语句的方法:

execute(String sql):执行各种SQL语句。返回一个boolean类型值。为true,表示执行的SQL语句具有查询结果,可通过Statement对象getResultSet()方法获取查询结果。比较少用。

executeUpdate(String sql):执行SQL的insert, update, delete语句。返回int类型的值,表示数据库中受SQL语句影响的记录数目

executeQuery(String sql):执行SQL的select语句。返回一个查询结果ResultSet对象。

 

nPreparedStatement接口

继承自Statement接口,用于执行预编译的SQL语句。

•PreparedStatement

–是Statement的子接口。

–表示预编译的 SQL 语句的对象。 SQL 语句被预编译并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。

–执行动态SQL语句。

•PreparedStatement 与Statement相比,优点

–1-具有预编译功能。提高执行效率

–2-执行动态SQL语句。提高灵活性

–3-因为可以执行动态的SQL语句,防止SQL注入。

– 

nCallableStatement接口

继承自Statement接口,用于执行存储过程。

 

nResultSet接口:

表示select查询语句得到的结果集。

 

lJava数据类型和SQL数据类型之间关系

lJDBC模版模式、

 

 

l作业:完善SMS系统。实现登录界面和主界面。如下图

登录判断说明:

使用Select password from user where name = ?;查询,返回ResultSet对象。对ResultSet rs进行遍历如果没有结果,表示该用户名有错误。如果有结果,则表示用户名存在,此时,可以得到从数据库中查出的密码。使用索引,提高执行效率。

dbPassword.equals(new String(password.getPassowrd()))

如果该语句返回true,表示输入的密码正确,成功登陆

如果为false,输入的密码错误,则提示信息

        主界面图:讲解JscrollPane, Jtable使用。

注册界面:

 

l事务(Transaction)

实际情况分析:

事务:

–数据库的事务是保证数据完整性的一种机制。

–事务是一组原子操作单元.一组Sql要么全部成功,有一条失败,则全部回退。简而言之,事务就是多行语句要么全部执行,要么全部不执行。

–事务的特性

原子性(atomicity):组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分。

一致性(consistency):在事务处理执行前后,数据库是一致的(两个账户要么都变,或者都不变)。

隔离性(isolcation):一个事务处理对另一个事务处理没有影响。

持续性(durability):事务处理的效果能够被永久保存下来 。

使用事务:

        Connection接口中,3个方法用于控制事务。

                 setAutoCommit(boolean autoCommit):设置是否自动提交事务

在JDBC API中,默认为自动提交事务。即:每一条操纵数据库的SQL语句代表一个事务,如果操作成功,数据库系统将自动提交事务,否则就撤销事务。通过将该方法设置为false,禁止自动提交事务,才可以把多条操纵数据库的SQL语句作为一个事务。

                 commit():提交事务

                 rollback():撤销事务

 

事务保存点:

SavePoint sp = null;//声明保存点

try{

 ……..

 sp = conn.setSavepoint();//设置保存点

 ……..

}catch(Exception e){

  if(conn != null && sp != null){

      conn.rollback(sp);

      conn.commit();提交以前的执行。

}

}

案例:

Connection conn = null;

                 ResultSet rs = null;

                 PreparedStatement ps = null;

                 Savepoint sp = null;

                 String sql = "update user set money = money - 100 where id = 1";

                 try{

                         conn = DBUtil.getConnection();

                         conn.setAutoCommit(false);

                         ps = conn.prepareStatement(sql);

                         ps.executeUpdate();

                         sp = conn.setSavepoint();

                        

                         sql = "update user set money = money - 1000 where id = 3";

                         ps.executeUpdate(sql);

                        

                         sql = "select money from user where id = 2";

                         rs = ps.executeQuery();

                        

                         float money = 0.0f;

                        

                         if(rs.next()){

                                  money = rs.getFloat("money");

                         }

                        

                         if(money > 400){

                                  throw new RuntimeException("超值了");

                         }

                        

                         sql = "update user set money = money + 10 where id = 2";

                         ps.executeUpdate();

                        

                         conn.commit();

                 }catch(RuntimeException e){

                         if(conn != null && sp != null){

                                  conn.rollback(sp);

                                  conn.commit();

                         }

                         throw e;

                 }

                 catch (SQLException e){

                         if(conn != null){

                                  conn.rollback();

                                  throw e;

                         }

                 }

                 finally{

                         DBUtil.close(rs, ps, conn);

                 }

        }

}

 

l批处理

–每次insert、update, delete效率比较低,在一条连接上,一次发送一批SQL语句,可以大幅提升增删改效率。

–客户端打包不能太多,可能会溢出。

–通常在执行批处理之前关闭事务的自动提交功能。这样可避免执行批量任务的过程中产生的错误对其他操作产生的影响。

案例:

Connection conn = null;

        PreparedStatement ps = null;

        String sql = "update bank set sum = sum + ? where name = ?";

        int[] rows = null;

        try        {

            conn = DBUtil.getConnection();

            // 执行批处理,关闭自动提交功能。

            conn.setAutoCommit(false);

            ps = conn.prepareStatement(sql);

            ps.setInt(1, 5000);

            ps.setString(2, "a");

            ps.addBatch();

            ps.setInt(1, -5000);

            ps.setString(2, "b");

            ps.addBatch();

            //执行批处理

            rows = ps.executeBatch();

        } catch (SQLException e){

            // TODO Auto-generated catch block

            e.printStackTrace();

        } finally{

            DBUtil.close(null, ps, conn);

        }

        System.out.println(Arrays.toString(rows));

l事务与批处理

–事务处理:底层是在数据库方存储SQL(没有提交事务的数据放在数据库的临时表空间),最后一次把临时表空间的数据提交到数据库服务器执行实现。(消耗数据库服务器内存)。

 

–SQL批处理:底层是在客户端把SQL存储起来,最后一次把客户端存储的数据发送到数据库服务器执行实现。(消耗客户端的内存)。较少使用。

 

l元数据

 

l使用JDBC调用存储过程

–存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中。

–用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。

–存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程

–优点:

•存储过程允许标准组件式编程

•存储过程能够实现较快的执行速度,提高性能

•存储过程能够减少网络流量

•存储过程可被作为一种安全机制来充分利用

 

两层架构中,存储过程使用较多;三层结构较少使用存储过程。

案例:

 

l三大范式:

–第一范式:关系模式中,每个属性不可再分。属性原子性。

•列不可再分

–非主属性完全依赖于主属性,即消除非主属性对主属性的部分函数依赖关系。

•满足第一范式的基础上,所有非主属性必须与主键完全依赖。

–第三范式:非主属性对主属性不存在传递函数依赖关系。

•第三范式总结:满足第二范式基础上,确保非主属性与主键直接相关,而不是间接的相关。

l  关系模型和对象模型

关系模型:

数据库中,表与表之间存在着一定的联系。通过外键来表示这种关联,而外键仅仅引用其他表或自己表中的主键,而不是一行数据。

表和表之间三种关系:

一对一:较少见

比如:wife表和husband表,此时设计表时,必须找出主从关系。

    create table husband(

        hid int primary key

    )

create table wife(

      wid int primary key

      hid int unique,

      constraint fk_one foreign key(hid) references husband(hid)

    )

    crete table wife(

        wid int primary key,

        constraint fk_one foreign key(wid) references husband(hid)

    )

一对多或多对一:最常见

比如:footballer表和team表。此时一般在多的一方建外键约束

    create table team

    (

       tid int primary key,

       tname varchar2(30)

)

create table footballer

(

    fid int primary key,

    name varchar(30) not null,

    tid int,

    constraint fk_multi foreign key(tid) references team(tid)

)

多对多:较常见

比如student表和course表。此时一般会建立一个中间表处理多对多的关系。

    create  table student(

       sno int primary key,

       name varchar(30) not null,

    )

       create table course(

          cid int primary key,

          cname varchar(30) not null

       )

       create table sc(

           sno int,

           cno int,

           constraint fk_sno foreign key(sno) references student(sno),

           constraint fk_cno foreign key(cno) references course(cno)

       )

对象模型:

Java中的类。操作数据库时,一般一个数据表会对应一个JavaBean(类)。此时如果表与表之间存在以上三种关系,则对应的类也要体现出这种关系。表与表之间通过外键引用即可,而类与类之间,引用的为对象。

类与类之间三种关系

一对一:

    class Husband{}

    class Wife{

        private Husband h;

}

class Wife{}

class Husband{

    private Wife w;

}

一对多或多对一:

    class Team{}

    class Footballer{//多方关联一方

        private Team t;

    }

    或

    class Footballer{}

    class Team{//一方关联多方

        private List<Footballer> t;

    }

多对多:

    多方关联多方

    class Student{

        private List<Course> c;

    }

    class Course{

        private List<Student> s;

    }

 

网络编程

l网络基础知识

1- TCP/IP四层协议

2- IP地址

IP:唯一标识网络中的计算机。

IP由什么组成:

        ip地址是由32位的二进制组成的。并且ip地址分为网络号和主机号

又因为32位的二进制,太难记忆,也难于使用。所以,我们把IP地址通过每八位划分,划分为四部分。00000000(0)~11111111(255)。我们把划分后的IP地址每一部分用十进制标识,且在每部分之间通过“.”划分。划分成如下表示形式

X1   .    X2   .    X3    .    X4--------IP地址的  “点分十进制”。

IP地址又分为IPV4和IPV6。

 

 

 

•X1.X2.X3.X4

•_ _ _ _ _ _ _ _   = X1

•IP = 网络号+主机号

特殊IP:

        1:127网段:用于回环测试。127.0.0.1表示本机

                 表示本机:IP,localhost,127.0.0.1,    .       主机名

        2:10.x.x.x:用于表示局域网。

        3:172.16.x.x~172.31.x.x:用于局域网

        4:192.168.x.x:用于局域网

3-网关代理

4- DNS

        DNS=Domain Name System

5- Port

–网络通信中,是两个应用程序通信。首先,先要找到对方电脑,然后再找电脑中的应用程序。

–如图,两台电脑QQ通信:1-A先找到B(IP);2-A中的QQ要找到B中的QQ(port)

–Port是标识电脑中的应用程序。

–端口号是用一个16位的整数来表达的,其范围为0~65535,其中0~1023为系统所保留,专门给那些通用的服务(well-known services),如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口为23,…因此,当我们编写通信程序时,应选择一个大于1023的数作为端口号,以免发生冲突.

IP地址和端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的最后终结点,它是TCP和UDP的基础。

 

lUDP和TCP

–UDP=user datagram protocal用户数据报协议

•不面向连接,提供不可靠服务

•具有无序性

•效率较高

•Ex:发短信

–TCP=transfer control protocal传输控制协议

•面向连接,提供可靠服务

•具有有序性

•效率较低

•Ex:打电话

•UDP和TCP如何选择?

–TCP协议和UDP协议各有各的用处。当对所传输的数据具有时序性和可靠性等要求时,应使用TCP协议;当传输的数据比较简单、对时序等无要求时,UDP协议能发挥更好的作用。

 

lInetAddress

–主要用于封装IP地址

 

lUDP编程

l练习:

lUDP发送端:

1-创建Socket服务端点,可以指定端口,也可以不指定。

        DatagramSocket ds = new DatagramSocket();

2-创建发送的数据报,一定要指定发送的数据,数据的长度,目的IP,目的端口。

        DatagramPacket dp = new DatagramPacket(data, data.length,InetAddress,port);

3-将数据报发送出去:

        ds.send(DatagramPacket dp);

4-关闭ds对象

        ds.close();

 

lUDP接受端:

1-创建Socket服务端点,一定要指定端口号。

        DatagramSocket ds = new DatagramSocket(8888);

2-创建接受的数据报

        DatagramPacket dp = new DatagramPacket(data, data.length);

3-将数据报发送出去:

        ds.receive(DatagramPacket dp);

        3.1-可以通过dp对象,得到客户端的信息

                 IP, PORT, DATA, LENGTH;

4-关闭ds对象

        ds.close();

 

lTCP编程

lTCP编程-服务器端

1-创建ServerSocket对象,并指定端口号

ServerSocket ss = new ServerSocket(port);

2-等待客户端连接  accept();且指定一个Socket对象处理客户端的通信。如果服务器端处理多个客户端时,需要使用线程。

Socket s = ss.accept();

3-通过socket对象,得到相对于客户端的输入流和输出流,与客户端进行通信。

s.getInputStream()和s.getOutputStream()

4-关闭对象

 

lTCP编程-客户端

1-创建Socket对象,且连接服务器

Socket s = new Socket(ip, port);

2-根据Socket对象,得到输入流和输出流,与服务器进行通信。

s.getInputStream()和s.getOutputStream()

3-关闭对象

 

lURL

–URL(Uniform Resource Locator)是统一资源定位器的简称,表示Internet上某一资源的地址。 Internet上的资源包括HTML文件、图象文件、声音文件、动画文件以及其他任何内容

–通过URL,就可以访问Internet。浏览器或其他程序通过解析给定的URL就可以在网络上查找相应的文件或其他资源。

http://www.youku.com:80/index.html

–属于应用层

 

lURLConnection

–抽象类 URLConnection 是所有类的超类,它代表应用程序和URL 之间的通信链接。

– 属于应用层

 

lURLConnection和URL

•URL表示一个统一资源定位符;只能接受服务器上资源的信息,只能读。(openStream方法内部,使用还是URLConnection中的内容)

•URLConnection:表示一个连接。可以与服务器进行读写通信。

 

反射

l什么是反射

–反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。

–JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

lClass

–Class<?>:表示一个正在运行的 Java 应用程序中的类和接口,是Reflection的起源。

–Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

–字节码文件只有在使用到它时,才加载到JVM.

获得Class对象三种方式

                 /1-直接通过类名.class得到Class对象

Class clazz1 = String.class;

        //2-通过对象.getClass()方法,得到Class对象

        String str = "aaa";

Class clazz3 = str.getClass();

        //3-通过Class.forName(包名.类名);,此方法,常用。

Class clazz4 = Class.forName("java.lang.String");

 

lJava反射机制主要提供了以下功能:

–在运行时判断任意一个对象所属的类;

–在运行时构造任意一个类的对象;

–在运行时判断任意一个类所具有的成员变量和方法;

–在运行时调用任意一个对象的方法;

–生成动态代理。

注意:

Class对象是反射的基础。如果要获得Field,Method,Constructor这些对象,必须先有Class对象。

 

lConstructor类

–1-得到Class对象;Class clazz = 类.class;

–2-通过Class对象得到指定的构造方法的对象

Constructor con = clazz.getDeclaredConstructor(class…paramType);

–3-通过构造方法的对象,创建类的对象

Object Con.newInstance(Object… objs);

–通过反射创建指定类的对象方式?

•类型.class.newInstance();根据空的构造函数,创建对象

•类型.class.getConstructor(Class..paramType).newInstance(Object… objs)//既可以是空的构造函数,也可以有参数的构造函数。

 

lField类

–1-创建Class对象 :Class clazz = 类型.class;

–2-通过该对象,得到具体某个属性对象.

Field f = clazz.getDeclaredField(name)

–3-设置属性值和得到属性值

f.set(Object, value);

f.get(Object);

–如果想访问私有属性,则只需f.setAccessible(true)

 

lMethod类

–1-创建Class对象 :Class clazz = 类型.class;

–2-通过该对象,得到具体某个方法对象.

Method m = clazz.getDeclaredMethod(name, Class…paramTypes)

–3-调用方法

                         m.invoke(object, Object … param)

–如果想访问私有方法,则只需m.setAccessible(true)

 

lArray

Array 类提供了动态创建和访问 Java 数组的方法。

•Arrays和Array?

•此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂

•Array 类提供了动态创建和访问 Java 数组的方法。

 

l作业:

–通过反射找出java.lang.Math 这个类的构造函数、属性和方法。

 

 

猜你喜欢

转载自blog.csdn.net/zqq3436/article/details/80306364
今日推荐