Java基础知识总结(绝对经典)

写代码:

1,明确需求。我要做什么?

2,分析思路。我要怎么做?1,2,3。

3,确定步骤。每一个思路部分用到哪些语句,方法,和对象。

4,代码实现。用具体的java语言代码把思路体现出来。

 

学习新技术的四点:

1,该技术是什么?

2,该技术有什么特点(使用注意):

3,该技术怎么使用。demo

4,该技术什么时候用?test。

-----------------------------------------------------------------------------------------------

一:java概述:

1991 年Sun公司的James Gosling(詹姆斯·高斯林)等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒、PDA等的微处理器;

1994年将Oak语言更名为Java;

 

Java的三种技术架构:

JAVAEE:Java Platform Enterprise Edition,开发企业环境下的应用程序,主要针对web程序开发;

JAVASE:Java Platform Standard Edition,完成桌面应用程序的开发,是其它两者的基础;

JAVAME:Java Platform Micro Edition,开发电子消费产品和嵌入式设备,如手机中的程序;

 

1,JDK:Java Development Kit,java的开发和运行环境,java的开发工具和jre。

2,JRE:Java Runtime Environment,java程序的运行环境,java运行的所需的类库+JVM(java虚拟机)。

3,配置环境变量:让java jdk\bin目录下的工具,可以在任意目录下运行,原因是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮我们去找指定的目录。

环境变量的配置:

    1):永久配置方式:JAVA_HOME=%安装路径%\Java\jdk

                      path=%JAVA_HOME%\bin

    2):临时配置方式:set path=%path%;C:\Program Files\Java\jdk\bin

特点:系统默认先去当前路径下找要执行的程序,如果没有,再去path中设置的路径下找。

classpath的配置:

    1):永久配置方式:classpath=.;c:\;e:\

    2):临时配置方式:set classpath=.;c:\;e:\

 

注意:在定义classpath环境变量时,需要注意的情况

如果没有定义环境变量classpath,java启动jvm后,会在当前目录下查找要运行的类文件;

如果指定了classpath,那么会在指定的目录下查找要运行的类文件。

还会在当前目录找吗?两种情况:

    1):如果classpath的值结尾处有分号,在具体路径中没有找到运行的类,会默认在当前目录再找一次。

    2):如果classpath的值结果出没有分号,在具体的路径中没有找到运行的类,不会再当前目录找。

    一般不指定分号,如果没有在指定目录下找到要运行的类文件,就报错,这样可以调试程序。

 

4,javac命令和java命令做什么事情呢?

    要知道java是分两部分的:一个是编译,一个是运行。

    javac:负责的是编译的部分,当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。 生成了jvm可以识别的字节码文件。也就是class文件,也就是java的运行程序。

    java:负责运行的部分.会启动jvm.加载运行时所需的类库,并对class文件进行执行.

    一个文件要被执行,必须要有一个执行的起始点,这个起始点就是main函数.

----------------------------------------------------------------------------------------------

二:java语法基础:

 

1,关键字:其实就是某种语言赋予了特殊含义的单词。

    保留字:其实就是还没有赋予特殊含义,但是准备日后要使用过的单词。

2,标示符:其实就是在程序中自定义的名词。比如类名,变量名,函数名。包含 0-9、a-z、$、_ ;

    注意:

    1),数字不可以开头。

    2),不可以使用关键字。

3,常量:是在程序中的不会变化的数据。

4,变量:其实就是内存中的一个存储空间,用于存储常量数据。

    作用:方便于运算。因为有些数据不确定。所以确定该数据的名词和存储空间。

    特点:变量空间可以重复使用。

什么时候定义变量?只要是数据不确定的时候,就定义变量。

 

变量空间的开辟需要什么要素呢?

    1,这个空间要存储什么数据?数据类型。

    2,这个空间叫什么名字啊?变量名称。

    3,这个空间的第一次的数据是什么? 变量的初始化值。

 

变量的作用域和生存期:

变量的作用域:

    作用域从变量定义的位置开始,到该变量所在的那对大括号结束;

生命周期:

    变量从定义的位置开始就在内存中活了;

    变量到达它所在的作用域的时候就在内存中消失了;

 

数据类型:

    1):基本数据类型:byte、short、int、long、float、double、char、boolean

    2):引用数据类型: 数组、类、接口。

级别从低到高为:byte,char,short(这三个平级)-->int-->float-->long-->double

自动类型转换:从低级别到高级别,系统自动转的;

强制类型转换:什么情况下使用?把一个高级别的数赋给一个别该数的级别低的变量;

 

运算符号:

    1)、算术运算符。

        + - * / %   %:任何整数模2不是0就是1,所以只要改变被模数就可以实现开关运算。

        +:连接符。

        ++,--

    2)、赋值运算符。

        =  += -= *= /= %=

    3)、比较运算符。

        特点:该运算符的特点是:运算完的结果,要么是true,要么是false。

    4)、逻辑运算符。

        &  |  ^  !   &&   ||

        逻辑运算符除了 !  外都是用于连接两个boolean类型表达式。

        &: 只有两边都为true结果是true。否则就是false。

        |:只要两边都为false结果是false,否则就是true

        ^:异或:和或有点不一样。

             两边结果一样,就为false。

             两边结果不一样,就为true.

        & 和 &&区别: & :无论左边结果是什么,右边都参与运算。

                      &&:短路与,如果左边为false,那么右边不参数与运算。

        | 和|| 区别:|:两边都运算。

                    ||:短路或,如果左边为true,那么右边不参与运算。

    5)、位运算符:用于操作二进制位的运算符。

        &  |  ^

        <<  >>   >>>(无符号右移)

    练习:对两个变量的数据进行互换。不需要第三方变量。

            int a  = 3,b = 5;-->b = 3,a = 5;

            a = a + b; a = 8;

            b = a - b; b = 3;

            a = a - b; a = 5;

            a = a ^ b;//

            b = a ^ b;//b = a ^ b ^ b = a

            a = a ^ b;//a = a ^ b ^ a = b;

        练习:高效的算出 2*8 = 2<<3;

5,语句。

    If  switch  do while   while  for

    这些语句什么时候用?

    1)、当判断固定个数的值的时候,可以使用if,也可以使用switch。

    但是建议使用switch,效率相对较高。

switch(变量){

   case 值:要执行的语句;break;

   …

   default:要执行的语句;

}

 工作原理:用小括号中的变量的值依次和case后面的值进行对比,和哪个case后面的值相同了

           就执行哪个case后面的语句,如果没有相同的则执行default后面的语句;

 细节:1):break是可以省略的,如果省略了就一直执行到遇到break为止;

       2):switch 后面的小括号中的变量应该是byte,char,short,int四种类型中的一种;

       3):default可以写在switch结构中的任意位置;如果将default语句放在了第一行,则不管expression与case中的value是否匹配,程序会从default开始执行直到第一个break出现。

    2)、当判断数据范围,获取判断运算结果boolean类型时,需要使用if。

    3)、当某些语句需要执行很多次时,就用循环结构。

    while和for可以进行互换。

    区别在于:如果需要定义变量控制循环次数。建议使用for。因为for循环完毕,变量在内存中释放。

 

break:作用于switch ,和循环语句,用于跳出,或者称为结束。

break语句单独存在时,下面不要定义其他语句,因为执行不到,编译会失败。当循环嵌套时,break只跳出当前所在循环。要跳出嵌套中的外部循环,只要给循环起名字即可,这个名字称之为标号。

 

continue:只作用于循环结构,继续循环用的。

作用:结束本次循环,继续下次循环。该语句单独存在时,下面不可以定义语句,执行不到。

 

6,函 数:为了提高代码的复用性,可以将其定义成一个单独的功能,该功能的体现就是java中的函数。函数就是体现之一。

java中的函数的定义格式:

    修饰符 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数1,…){

       执行语句;

       return 返回值;

    }

当函数没有具体的返回值时,返回的返回值类型用void关键字表示。

如果函数的返回值类型是void时,return语句可以省略不写的,系统会帮你自动加上。

return的作用:结束函数。结束功能。

 

如何定义一个函数?

    函数其实就是一个功能,定义函数就是实现功能,通过两个明确来完成:

    1)、明确该功能的运算完的结果,其实是在明确这个函数的返回值类型。

    2)、在实现该功能的过程中是否有未知内容参与了运算,其实就是在明确这个函数的参数列表(参数类型&参数个数)。

函数的作用:

1)、用于定义功能。

2)、用于封装代码提高代码的复用性。

注意:函数中只能调用函数,不能定义函数。

主函数:

    1)、保证该类的独立运行。

    2)、因为它是程序的入口。

    3)、因为它在被jvm调用。

 

函数定义名称是为什么呢?

答:1)、为了对该功能进行标示,方便于调用。

    2)、为了通过名称就可以明确函数的功能,为了增加代码的阅读性。

 

重载的定义是:在一个类中,如果出现了两个或者两个以上的同名函数,只要它们的参数的个数,或者参数的类型不同,即可称之为该函数重载了。

如何区分重载:当函数同名时,只看参数列表。和返回值类型没关系。

 

7,数 组:用于存储同一类型数据的一个容器。好处:可以对该容器中的数据进行编号,从0开始。数组用于封装数据,就是一个具体的实体。

如何在java中表现一个数组呢?两种表现形式。

1)、元素类型[] 变量名 = new 元素类型[元素的个数];

2)、元素类型[] 变量名 = {元素1,元素2...};

元素类型[] 变量名 = new 元素类型[]{元素1,元素2...};

---------------------------------------------------------

//二分查找法。必须有前提:数组中的元素要有序。

    public static int halfSeach_2(int[] arr,int key){

        int min,max,mid;

        min = 0;

        max = arr.length-1;

        mid = (max+min)>>1; //(max+min)/2;

        while(arr[mid]!=key){

            if(key>arr[mid]){

                min = mid + 1;

            }

            else if(key<arr[mid])

                max = mid - 1;

            if(max<min)

                return -1;

            mid = (max+min)>>1;

        }

        return mid;

    }

---------------------------------------------------------

java分了5片内存。

1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。

栈:存储的都是局部变量 ( 函数中定义的变量,函数上的参数,语句中的变量 );

    只要数据运算完成所在的区域结束,该数据就会被释放。

堆:用于存储数组和对象,也就是实体。啥是实体啊?就是用于封装多个数据的。

1:每一个实体都有内存首地址值。

2:堆内存中的变量都有默认初始化值。因为数据类型不同,值也不一样。

3:垃圾回收机制。

----------------------------------------------------------------------------------------------

三:面向对象:★★★★★

特点:1:将复杂的事情简单化。

2:面向对象将以前的过程中的执行者,变成了指挥者。

3:面向对象这种思想是符合现在人们思考习惯的一种思想。

 

过程和对象在我们的程序中是如何体现的呢?过程其实就是函数;对象是将函数等一些内容进行了封装。

 

匿名对象使用场景:

1:当对方法只进行一次调用的时候,可以使用匿名对象。

2:当对象对成员进行多次调用时,不能使用匿名对象。必须给对象起名字。

 

在类中定义其实都称之为成员。成员有两种:

1:成员变量:其实对应的就是事物的属性。

2:成员函数:其实对应的就是事物的行为。

 

所以,其实定义类,就是在定义成员变量和成员函数。但是在定义前,必须先要对事物进行属性和行为的分析,才可以用代码来体现。

 

private int age;//私有的访问权限最低,只有在本类中的访问有效。

注意:私有仅仅是封装的一种体现形式而已。

 

私有的成员:其他类不能直接创建对象访问,所以只有通过本类对外提供具体的访问方式来完成对私有的访问,可以通过对外提供函数的形式对其进行访问。

好处:可以在函数中加入逻辑判断等操作,对数据进行判断等操作。

 

总结:开发时,记住,属性是用于存储数据的,直接被访问,容易出现安全隐患,所以,类中的属性通常被私有化,并对外提供公共的访问方法。

这个方法一般有两个,规范写法:对于属性 xxx,可以使用setXXX(),getXXX()对其进行操作。

 

类中怎么没有定义主函数呢?

注意:主函数的存在,仅为该类是否需要独立运行,如果不需要,主函数是不用定义的。

主函数的解释:保证所在类的独立运行,是程序的入口,被jvm调用。

 

成员变量和局部变量的区别:

1:成员变量直接定义在类中。

   局部变量定义在方法中,参数上,语句中。

2:成员变量在这个类中有效。

局部变量只在自己所属的大括号内有效,大括号结束,局部变量失去作用域。

3:成员变量存在于堆内存中,随着对象的产生而存在,消失而消失。

局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放。

 

构造函数:用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种。

特点:

1:该函数的名称和所在类的名称相同。

2:不需要定义返回值类型。

3:该函数没有具体的返回值。

记住:所有对象创建时,都需要初始化才可以使用。

 

注意事项:一个类在定义时,如果没有定义过构造函数,那么该类中会自动生成一个空参数的构造函数,为了方便该类创建对象,完成初始化。如果在类中自定义了构造函数,那么默认的构造函数就没有了。

 

一个类中,可以有多个构造函数,因为它们的函数名称都相同,所以只能通过参数列表来区分。所以,一个类中如果出现多个构造函数。它们的存在是以重载体现的。

 

构造函数和一般函数有什么区别呢?

1:两个函数定义格式不同。

2:构造函数是在对象创建时,就被调用,用于初始化,而且初始化动作只执行一次。

    一般函数,是对象创建后,需要调用才执行,可以被调用多次。

  

什么时候使用构造函数呢?

分析事物时,发现具体事物一出现,就具备了一些特征,那就将这些特征定义到构造函数内。

 

构造代码块和构造函数有什么区别?

构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块,只要对象一建立,就会调用这个代码块。

构造函数:是给与之对应的对象进行初始化,它具有针对性。

 

“Person p = new Person();”

创建一个对象都在内存中做了什么事情?

1:先将硬盘上指定位置的Person.class文件加载进内存。

2:执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配了一个变量p。

3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new

4:在该实体空间中进行属性的空间分配,并进行了默认初始化。

5:对空间中的属性进行显示初始化。

6:进行实体的构造代码块初始化。

7:调用该实体对应的构造函数,进行构造函数初始化。()

8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)

--------------------------------------------------------------------------------------------

封 装(面向对象特征之一):是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。

好处:将变化隔离;便于使用;提高重用性;安全性。

封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。

 

This:代表对象,就是所在函数所属对象的引用。

this到底代表什么呢?哪个对象调用了this所在的函数,this就代表哪个对象,就是哪个对象的引用。

开发时,什么时候使用this呢?

在定义功能时,如果该功能内部使用到了调用该功能的对象,这时就用this来表示这个对象。

 

this 还可以用于构造函数间的调用。

调用格式:this(实际参数);

this对象后面跟上 .  调用的是成员属性和成员方法(一般方法);

this对象后面跟上 () 调用的是本类中的对应参数的构造函数。

 

注意:用this调用构造函数,必须定义在构造函数的第一行。因为构造函数是用于初始化的,所以初始化动作一定要执行。否则编译失败。

 

static:★★★ 关键字,是一个修饰符,用于修饰成员(成员变量和成员函数)。

特点:

1,想要实现对象中的共性数据的对象共享,可以将这个数据进行静态修饰。

2,被静态修饰的成员,可以直接被类名所调用。也就是说,静态的成员多了一种调用方式。类名.静态方式。

3,静态随着类的加载而加载,而且优先于对象存在。

 

弊端:

1,有些数据是对象特有的数据,是不可以被静态修饰的。因为那样的话,特有数据会变成对象的共享数据。这样对事物的描述就出了问题。所以,在定义静态时,必须要明确,这个数据是否是被对象所共享的。

2,静态方法只能访问静态成员,不可以访问非静态成员。

因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。

3,静态方法中不能使用this,super关键字。

因为this代表对象,而静态在时,有可能没有对象,所以this无法使用。

4,主函数是静态的。

 

什么时候定义静态成员呢?或者说:定义成员时,到底需不需要被静态修饰呢?

成员分两种:

1,成员变量。(数据共享时静态化)

该成员变量的数据是否是所有对象都一样:

如果是,那么该变量需要被静态修饰,因为是共享的数据。

如果不是,那么就说这是对象的特有数据,要存储到对象中。

2,成员函数。(方法中没有调用特有数据时就定义成静态)

    如果判断成员函数是否需要被静态修饰呢?

    只要参考,该函数内是否访问了对象中的特有数据:

    如果有访问特有数据,那方法不能被静态修饰。

    如果没有访问过特有数据,那么这个方法需要被静态修饰。

 

成员变量和静态变量的区别:

1,成员变量所属于对象,所以也称为实例变量。

静态变量所属于类,所以也称为类变量。

2,成员变量存在于堆内存中。

静态变量存在于方法区中。

3,成员变量随着对象创建而存在,随着对象被回收而消失。

静态变量随着类的加载而存在,随着类的消失而消失。

4,成员变量只能被对象所调用。

静态变量可以被对象调用,也可以被类名调用。

所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。

 

静态的注意:静态的生命周期很长。

静态代码块:就是一个有静态关键字标示的一个代码块区域,定义在类中。

作用:可以完成类的初始化,静态代码块随着类的加载而执行,而且只执行一次(new 多个对象就只执行一次)。如果和主函数在同一类中,优先于主函数执行。

 

Public:访问权限最大。

static:不需要对象,直接类名即可。

void:主函数没有返回值。

Main:主函数特定的名称。

(String[] args):主函数的参数,是一个字符串数组类型的参数,jvm调用main方法时,传递的实际参数是 new String[0]。

 

jvm默认传递的是长度为0的字符串数组,我们在运行该类时,也可以指定具体的参数进行传递。可以在控制台,运行该类时,在后面加入参数。参数之间通过空格隔开。jvm会自动将这些字符串参数作为args数组中的元素,进行存储。

 

静态代码块、构造代码块、构造函数同时存在时的执行顺序:静态代码块 &agrave; 构造代码块 &agrave; 构造函数;

 

生成Java帮助文档:命令格式:javadoc –d 文件夹名 –auther –version *.java

/**     //格式

*类描述

*@author 作者名

*@version 版本号

*/

/**

*方法描述

*@param  参数描述

*@return  返回值描述

*/

---------------------------------------------------------------------------------------------

设计模式:解决问题最行之有效的思想。是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

 

java中有23种设计模式:

单例设计模式:★★★★★

解决的问题:保证一个类在内存中的对象唯一性。

比如:多程序读取一个配置文件时,建议配置文件封装成对象。会方便操作其中数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。

 

Runtime()方法就是单例设计模式进行设计的。

 

如何保证对象唯一性呢?

思想:

1,不让其他程序创建该类对象。

2,在本类中创建一个本类对象。

3,对外提供方法,让其他程序获取这个对象。

 

步骤:

1,因为创建对象都需要构造函数初始化,只要将本类中的构造函数私有化,其他程序就无法再创建该类对象;

2,就在类中创建一个本类的对象;

3,定义一个方法,返回该对象,让其他程序可以通过方法就得到本类对象。(作用:可控)

 

代码体现:

1,私有化构造函数;

2,创建私有并静态的本类对象;

3,定义公有并静态的方法,返回该对象。

---------------------------------------------

//饿汉式

class Single{

    private Single(){} //私有化构造函数。

private static Single s = new Single(); //创建私有并静态的本类对象。

    public static Single getInstance(){ //定义公有并静态的方法,返回该对象。

        return s;

    }

}

---------------------------------------------

//懒汉式:延迟加载方式。

class Single2{

    private Single2(){}

private static Single2 s = null;

    public static Single2 getInstance(){

        if(s==null)

            s = new Single2();

        return s;

    }

}

-------------------------------------------------------------------------------------------------

继 承(面向对象特征之一)

好处:

1:提高了代码的复用性。

2:让类与类之间产生了关系,提供了另一个特征多态的前提。

 

父类的由来:其实是由多个类不断向上抽取共性内容而来的。

java中对于继承,java只支持单继承。java虽然不直接支持多继承,但是保留了这种多继承机制,进行改良。

 

单继承:一个类只能有一个父类。

多继承:一个类可以有多个父类。

 

为什么不支持多继承呢?

因为当一个类同时继承两个父类时,两个父类中有相同的功能,那么子类对象调用该功能时,运行哪一个呢?因为父类中的方法中存在方法体。

但是java支持多重继承。A继承B  B继承C  C继承D。

多重继承的出现,就有了继承体系。体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。

所以,一个体系要想被使用,直接查阅该系统中的父类的功能即可知道该体系的基本用法。那么想要使用一个体系时,需要建立对象。建议建立最子类对象,因为最子类不仅可以使用父类中的功能。还可以使用子类特有的一些功能。

 

简单说:对于一个继承体系的使用,查阅顶层父类中的内容,创建最底层子类的对象。

 

子父类出现后,类中的成员都有了哪些特点:

1:成员变量。

     当子父类中出现一样的属性时,子类类型的对象,调用该属性,值是子类的属性值。

     如果想要调用父类中的属性值,需要使用一个关键字:super

     This:代表是本类类型的对象引用。

     Super:代表是子类所属的父类中的内存空间引用。

     注意:子父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了。

2:成员函数。

当子父类中出现了一模一样的方法时,建立子类对象会运行子类中的方法。好像父类中的方法被覆盖掉一样。所以这种情况,是函数的另一个特性:覆盖(复写,重写)

什么时候使用覆盖呢?当一个类的功能内容需要修改时,可以通过覆盖来实现。

3:构造函数。

发现子类构造函数运行时,先运行了父类的构造函数。为什么呢?

原因:子类的所有构造函数中的第一行,其实都有一条隐身的语句super();

super(): 表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。而super():是在调用父类中空参数的构造函数。

为什么子类对象初始化时,都需要调用父类中的函数?(为什么要在子类构造函数的第一行加入这个super()?)

因为子类继承父类,会继承到父类中的数据,所以必须要看父类是如何对自己的数据进行初始化的。所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程。

 

注意:子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super();

如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数。

如果子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数。

 

问题:super()和this()是否可以同时出现的构造函数中。

两个语句只能有一个定义在第一行,所以只能出现其中一个。

 

super()或者this():为什么一定要定义在第一行?

因为super()或者this()都是调用构造函数,构造函数用于初始化,所以初始化的动作要先完成。

 

继承的细节:

什么时候使用继承呢?

当类与类之间存在着所属关系时,才具备了继承的前提。a是b中的一种。a继承b。狼是犬科中的一种。

英文书中,所属关系:" is a "

注意:不要仅仅为了获取其他类中的已有成员进行继承。

 

所以判断所属关系,可以简单看,如果继承后,被继承的类中的功能,都可以被该子类所具备,那么继承成立。如果不是,不可以继承。

 

细节二:

在方法覆盖时,注意两点:

1:子类覆盖父类时,必须要保证,子类方法的权限必须大于等于父类方法权限可以实现继承。否则,编译失败。

2:覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖)

 

继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是需要被继承,或者复写的。

这时如何解决问题呢?介绍一个关键字,final:最终。

 

final特点:

1:这个关键字是一个修饰符,可以修饰类,方法,变量。

2:被final修饰的类是一个最终类,不可以被继承。

3:被final修饰的方法是一个最终方法,不可以被覆盖。

4:被final修饰的变量是一个常量,只能赋值一次。

 

    其实这样的原因的就是给一些固定的数据起个阅读性较强的名称。

    不加final修饰不是也可以使用吗?那么这个值是一个变量,是可以更改的。加了final,程序更为严谨。常量名称定义时,有规范,所有字母都大写,如果由多个单词组成,中间用 _ 连接。

 

抽象类: abstract

抽象:不具体,看不明白。抽象类表象体现。

在不断抽取过程中,将共性内容中的方法声明抽取,但是方法不一样,没有抽取,这时抽取到的方法,并不具体,需要被指定关键字abstract所标示,声明为抽象方法。

抽象方法所在类一定要标示为抽象类,也就是说该类需要被abstract关键字所修饰。

 

抽象类的特点:

1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。

2:抽象方法只定义方法声明,并不定义方法实现。

3:抽象类不可以被创建对象(实例化)。

4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。

 

抽象类的细节:

1:抽象类中是否有构造函数?有,用于给子类对象进行初始化。

2:抽象类中是否可以定义非抽象方法?

    可以。其实,抽象类和一般类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。所以抽象类和一般类在定义上,都是需要定义属性和行为的。只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分。

3:抽象关键字abstract和哪些不可以共存?final , private , static

4:抽象类中可不可以不定义抽象方法?可以。抽象方法目的仅仅为了不让该类创建对象。

-----------------------------------------------------------------------------------------------

模板方法设计模式:

解决的问题:当功能内部一部分实现时确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。

abstract class GetTime{

    public final void getTime(){ //此功能如果不需要复写,可加final限定

        long start = System.currentTimeMillis();

        code(); //不确定的功能部分,提取出来,通过抽象方法实现

        long end = System.currentTimeMillis();

        System.out.println("毫秒是:"+(end-start));

    }

    public abstract void code(); //抽象不确定的功能,让子类复写实现

}

class SubDemo extends GetTime{

    public void code(){ //子类复写功能方法

        for(int y=0; y<1000; y++){

            System.out.println("y");

        }

    }

}

 

一:对象模块。

一.初始化

1.对this.super,构造函数,构造代码块,静态代码块总结。

this:代表当前对象,也就是所在函数所属对象的引用。

this对象后面加.调用的是对象的成员变量和方法。(this.say());

this对象后面加(),调用的是本类中对应参数的构造函数。

 

super:代表父类,也就是当前类的父类。

使用方式与this类似。

 

构造函数:用于当对对象初始化时调用的特殊函数,只在执行一次。

在构造函数中使用this或者super,必须定义在构造函数的第一行。如果没有用到,那么构造函数的第一句会默认的加上super();

构造代码块:是给所有的对象进行初始化,也就是,所有的对象都会调用的一个代码块,只要对象一建,就会调用这个代码块,用于给不同对象的共性初始化,优先于构造函数执行。

格式:{

代码。。。。。。

}

静态代码块:一个用static关键字标示的一个代码块区域,定义在类中。可以完成类的初始化,静态代码块会随着类的加载而执行一次(new多个对象也是只执行一次)。如果和主函数在同一个类中,优先于主函数执行。

格式:static{

代码。。。。。

}

三种初始化的执行顺序: 静态代码块--->构造代码块------>构造函数。

 

二.继承(extends):

1.重写和重载

重写:覆盖父类已有的方法,子父类方法必须一模一样。(包括返回值,子类复写父类方法时访问权限必须比父类大或者同级。方法要么都静态,要么都不静态)。

重载:只在本类中的多个方法,只看同名函数的参数列表。

子类初始化时,先初始化父类的方法和变量,在初始化自己的。

三.接口(implements)

1.实现

接口可以被多实现,类继承只能单继承。

接口与接口之间存在着继承关系,接口可以多继承接口。

 

四.多态

体现:父类或者接口的引用指向自己的子类对象。(注意:在使用多态时,要访问子类的方法,要求父类中必须对该方法进行了声明或者定义)。
多态在子父类中的成员上的体现的特点:

成员变量:

编译时期:参考的引用类型变量所属的类中是否有调用的成员。(编译时期不产生对象,只检查语法错误)。

运行时期:也是参考引用类型变量所属的类中是否有调用的成员。

简单总结:成员变量——编译运行都看 = 左边。

 

非静态成员函数:

编译时期:参考引用类型变量所属的类中是否有调用的方法。

运行时期:参考的是对象所属的类中是否有调用的方法。

原因:因为在子父类的非晶态成员函数中有一个特性:重写(覆盖)。

简单总结:成员函数——编译看 = 左边,  运行看 = 右边。 

静态函数:

编译时期:参考引用类型变量所属的类中是否有调用的方法。

运行时期:参考的是引用类型所属的类中是否有调用的方法。

原因:因为是静态方法,所以是不属于对象的,而是属于该方法所在的类。

简单总结:成员函数——编译运行看 = 左边,

 

 

五:内部类。

特点:内部类可以直接访问外部类中的成员,而外部类想要访问内部类,必须要建立内部类的对象。

-------------------------------------------------------------------------------------------------------------------------------

class Outer{

int num = 4;

class Inner {

void show(){

System.out.println("inner show run" + num);

}

}

public void method(){

Inner in = new Inner();//创建内部类的对象

in.show();//调用内部类的方法

}

-------------------------------------------------------------------------------------------------------------------------------

当内部类定义在外部类的成员变量位置。可以使用一些成员修饰符进行修饰默认,private,static.

1.默认修饰符。

直接访问内部类格式:外部类名.内部类名 变量名 =  new 外部类对象.内部类对象;

Outer.Inner in = new Quter.new Inner();

上面这种方式比较少见,因为内部类本来就是为了封装,想要获取内部类对象通常都是通过外部类的方法来获取,这样可以对内部类对象进行控制。

 

2.private修饰符。

通常内部类被封装,都会被私有化。

 

3.静态修饰符。

如果内部类被静态修饰,相当于外部类,会出现访问局限性,只能访问外部类中的静态成员。

 

注意:如果内部类中定义了静态成员,那么该内部类必须是静态的。
     当外部类的静态方法访问内部类时,内部类也必须是静态的。

外部其他类中,直接访问static内部类的非静态成员:New Outer.Inner().show();

外部其他类中,直接访问static内部类的静态成员:Outer.Inner.show();

 

一般内部类经过编译后文件名为:“外部类名$内部类名.class”;

 

当内部类被定义在局部位置上时。

1.不可以被成员修饰符修饰(例如:static,static是修饰成员的)

 

2.可以访问外部类中成员,因为还持有外部类中的引用,(外部内.this),但不可以访问它所在的局部中的变量,只能访问被final修饰的局部变量。

 

匿名内部类:

意义:没有名字的内部类,是内部类的简化形式,一般内部类只用一次的话就可以用这种形式。匿名内部类其实就是一个匿名子类对象,想要定义匿名内部类:需要前提,内部类必须实现一个接口或者继承一个类。

匿名内部类的格式:new 父类名&接口名(){定义子类成员或者覆盖父类方法}.方法

case:

new Object(){

void show(){

System.out.prinlt("show run");

}

} .show();

六:异常。

1.使用throws来在方法上标识(声明),方法可能会出现异常,当调用者检查到有标识时有必须要进行处理,要么接着抛,要么try。否则会出现编译失败

声明格式:throws 异常类,异常类。。。。。并不是所有的异常都需要声明,RuntimeException(运行时异常)类及其子类可以不用声明。

 

2.捕获异常try{}catch(){}块来捕捉时,要注意有多个catch时,如果有父类的Exception语句块,一定要放在下面.

 

3.throw用于抛出异常对象。

 

异常,在子父类进行覆盖时,有以下特点:

1.当子类覆盖父类的方法时,如果父类的方法抛出了异常,那么子类的方法要么不抛出异常,要么就抛出父类异常或者该异常的子类,不能抛出其他异常。如果父类没有抛出异常,那么子类只能try不能throws.

2.如果父类抛出了多个异常,那么子类在覆盖时只能抛出父类异常的子集。

3.如果这个异常子类无法处理,已经影响了子类方法的具体运算,这时可以在子类方法中,通过throw抛出RuntimeException异常或者其子类,这样,子类方法就不需要throws进行声明。

注意:throw下面不能写语句,因为执行不到(会出现编译错误,和return,break后面不能写代码类似),但是注意如果是分支结构,也就是说if语句内写是可以的。

七:多线程。

1.相关概念。

进程:正在进行中的程序,就是一个应用程序运行时的内存分配空间

线程:进程中一个程序的执行控制单元,一条执行路径。

进程负责的是应用程序的空间的标示,线程负责的是应用程序的执行顺序。

 

cpu随机性原理:因为cpu的快速切换造成,那个线程获取到了CPU的执行权,那个线程就执行。

 

2.线程的几种状态:

被创建:start()。

运行:具备执行资格,同时具备执行权。

冻结:sleep(time),wait()-----notify()唤醒,线程冻结(沉睡),释放了执行权,同时释放了执行资格。

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权。

消亡:stop();

3.两种线程的创建方式

第一种方式:继承Thread,由子类复写run方法。

步骤:1.定义类继承Thread类。

2.目的是复写run方法,将要让线程运行的代码都存储到run方法中。

3.通过创建Thread类的子类对象,创建线程对象。

4.调用线程的start方法,开启线程,并执行run方法。

 

第二种方式:实现一个Runable接口。

步骤:1.定义类实现Runnable接口。

2.覆盖接口中的run方法。(用于封装线程要运行的代码)。

3.通过Thread类创建线程对象。

4.将实现了Runnable接口的子类作为实际参数传递给Thread类中的构造函数。(为什么要这么做?是为了让线程对象明确要运行的run方法所属的对象)。

5.调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。

 

一般情况推荐使用第二种方式,可以避免单继承。

 

4.线程安全问题

通过图可以发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据,那么会导致错误数据产生。

产生的两个因素:1.多条线程在操作共享数据。2.有多条语句对共享数据进行运算。

原因:这多条语句,在某一个时刻被一个线程执行是,还没执行完,cpu时间片到了,被其他线程执行了。

 

八:同步(锁)。

为了解决上面所说的线程安全问题而产生的技术,解决的思路就是:加同步,加锁,将要操作共享数据的语句在某一时段让一个线程执行完,在线程执行过程中,其他线程不能进来执行。

 

1.Java中提供了一个解决方式,就是同步代码块。也就是锁。

格式:

synchronized (对象){ //任意对象都可以,这个对象就是锁

//需要被同步的代码

}

 

定义同步的前提:

1.必须要有两个或者两个以上的线程,才需要同步。

2.多个线程必须保证使用的是同一个锁

 

2.另一种表现形式就是同步函数:就是将同步关键字定义在函数上,让函数具备了同步性。

1.同步函数所使用的锁是this对象。

2.当同步函数被static修饰时,由于此时的函数是属于类的,这时可能还没有产生该类的对象,但是该类的字节码文件加载进了内存就已经被封装成了对象,所以此时的锁就是字节码文件对象,也就是类名.class对象。

 

 

3.关于同步代码块和同步函数的区别?

同步代码块使用的锁可以是任意对象。

同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

 

4.死锁。多个同步进行嵌套导致,相互等待。

避免死锁:线程通信→等待唤醒机制,涉及的方法:

wait:将同步中的线程处理为冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。

notify:唤醒线程池中某一个线程。

notifyAll:唤醒线程池中所有的线程。

 

注意:1.这些方法都需要定义在同步中。2.因为这些方法都必须要标示所属的锁。因为A锁上的wait只能,让A锁去唤醒notify。(A.wait()----->A.notify)。

 

wait和sleep区别:分析这两个方法。从执行权和锁角度来分析。

wait:可以指定时间,也可以不指定时间,如果不指定时间,只能由对应的notify或者notifyAll来唤醒。  线程会释放执行权,而且线程也会释放锁。

sleep:必须指定时间,时间到线程自动由冻结状态转为运行状态(或者临时阻塞状态)。  线程会释放执行权,但是不会释放锁。

 

九:字符串,字符容器。

1.关于字符串的方法就不过多提了,就简单的说下这里容易出错的几个概念。

字符串特点:字符串一旦被初始化,就不可以改变,存放在方法区的常量池中。只要出现了“ ”(双引号)的数据那么就是字符串对象。关于

这里简单说一下关于Java的内存分布:1:寄存器 2:本地方法区 (静态方法,常量)3:方法区  4:栈(局部变量)  5:堆(实体,就是对象和数组)。

 

2.字符容器:StringBuffer 和  StringBuilder。

StringBuffer特点:1.初始容量为16个字符。

2.可以对字符串内容进行修改。

3.可变长度。

4.缓存区中可以存储任意类型的数据。

5.最终需要变成字符串。

6.最重要的线程安全的。

 

StringBuilder:与buffer一模一样,只是它是线程不安全的。

 

总结:多线程操作,使用StringBuffer安全。单线程使用StringBuilder效率高。

 

十:集合框架:用于存储数据的容器。

特点:

1.对象封装数据,对象多了也需要存储。集合用于存储对象。

2.对象的个数确定可以使用数组,但不确定个数怎么办,可以使用集合,因为集合时可变长度

 

集合与数组的区别:

1.数组是固定长度:集合可变长度的。

2.数组可以存储基本数据类型,也可以存储引用数据类型。集合只能存储引用数据类型。

3.数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

--< java.util >-- Collection接口:

Collection:

|--List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。

|--Set:无序(存入和取出顺序有可能不一致),不可以存储重复元素。必须保证元素唯一性。

 

--< java.util >-- Iterator接口:

迭代器:是一个接口。作用:用于取集合中的元素。

 boolean

hasNext()  如果仍有元素可以迭代,则返回 true。

 E

next()   返回迭代的下一个元素。

 void

remove()  从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。

每一个集合都有自己的数据结构,都有特定的取出自己内部元素的方式。为了便于操作所有的容器,取出元素。将容器内部的取出方式按照一个统一的规则向外提供,这个规则就是Iterator接口。

也就说,只要通过该接口就可以取出Collection集合中的元素,至于每一个具体的容器依据自己的数据结构,如何实现的具体取出细节,这个不用关心,这样就降低了取出元素和具体集合的耦合性。

 

-< java.util >-- List接口:

List本身是Collection接口的子接口,具备了Collection的所有方法。现在学习List体系特有的共性方法,查阅方法发现List的特有方法都有索引,这是该集合最大的特点。

List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。

|--ArrayList:底层的数据结构是数组,线程不同步,ArrayList替代了Vector,查询元素的速度非常快。

|--LinkedList:底层的数据结构是链表,线程不同步,增删元素的速度非常快。

|--Vector:底层的数据结构就是数组,线程同步的,Vector无论查询和增删都巨慢。

对于List的遍历,不仅可以使用Iterator接口,也可以使用下表(索引)来遍历,list.get(index);

--< java.util >-- Set接口:

Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。

|--HashSet:底层数据结构是哈希表,线程是不同步的。无序,高效;

HashSet集合保证元素唯一性:通过元素的hashCode方法,和equals方法完成的。

当元素的hashCode值相同时,才继续判断元素的equals是否为true。

如果为true,那么视为相同元素,不存。如果为false,那么存储。

如果hashCode值不同,那么不判断equals,从而提高对象比较的速度。

       |--LinkedHashSet:有序,hashset的子类。

|--TreeSet:对Set集合中的元素的进行指定顺序的排序。不同步。TreeSet底层的数据结构就是二叉树。

 

哈希表的原理:

1,对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。

2,哈希值就是这个元素的位置。

3,如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。

4,存储哈希值的结构,我们称为哈希表。

5,既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。

这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。

 6.在HashSet中尽量不要改变参与运算hashCode值的变量,以防止内存泄露。。

对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。

对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。

 

TreeSet:

用于对Set集合进行元素的指定顺序排序,排序需要依据元素自身具备的比较性。

如果元素不具备比较性,在运行时会发生ClassCastException异常。

所以需要元素实现Comparable接口,强制让元素具备比较性,复写compareTo方法。

依据compareTo方法的返回值,确定元素在TreeSet数据结构中的位置。

TreeSet方法保证元素唯一性的方式:就是参考比较方法的结果是否为0,如果return 0,视为两个对象重复,不存。

 

注意:在进行比较时,如果判断元素不唯一,比如,同姓名,同年龄,才视为同一个人。

在判断时,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。

 

TreeSet集合排序有两种方式,Comparable和Comparator区别:

1:让元素自身具备比较性,需要元素对象实现Comparable接口,覆盖compareTo方法。

2:让集合自身具备比较性,需要定义一个实现了Comparator接口的比较器,并覆盖compare方法,并将该类对象作为实际参数传递给TreeSet集合的构造函数。

第二种方式较为灵活。

 

Map集合:

|--Hashtable:底层是哈希表数据结构,是线程同步的。不可以存储null键,null值。

|--HashMap:底层是哈希表数据结构,是线程不同步的。可以存储null键,null值。替代了Hashtable.

|--TreeMap:底层是二叉树结构,可以对map集合中的键进行指定顺序的排序。

 

Map集合存储和Collection有着很大不同:

Collection一次存一个元素;Map一次存一对元素。

Collection是单列集合;Map是双列集合。

Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。

特点:要保证map集合中键的唯一性。

 

想要获取map中的所有元素:

原理:map中是没有迭代器的,collection具备迭代器,只要将map集合转成Set集合,可以使用迭代器了。之所以转成set,是因为map集合具备着键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。

★ 把map集合转成set的方法:

Set keySet();

Set entrySet();//取的是键和值的映射关系。

Entry就是Map接口中的内部接口;

为什么要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对。

---------------------------------------------------------

取出map集合中所有元素的方式一:keySet()方法。

可以将map集合中的键都取出存放到set集合中。对set集合进行迭代。迭代完成,再通过get方法对获取到的键进行值的获取。

Set keySet = map.keySet();

Iterator it = keySet.iterator();

while(it.hasNext()) {

Object key = it.next();

Object value = map.get(key);

System.out.println(key+":"+value);

}

-------------------------------------------------------

取出map集合中所有元素的方式二:entrySet()方法。

Set entrySet = map.entrySet();

Iterator it = entrySet.iterator();

while(it.hasNext()) {

Map.Entry  me = (Map.Entry)it.next();

System.out.println(me.getKey()+"::::"+me.getValue());

}

--------------------------------------------------------

使用集合的技巧:

看到Array就是数组结构,有角标,查询速度很快。

看到link就是链表结构:增删速度快,而且有特有方法。addFirst; addLast; removeFirst(); removeLast(); getFirst();getLast();

看到hash就是哈希表,就要想要哈希值,就要想到唯一性,就要想到存入到该结构的中的元素必须覆盖hashCode,equals方法。

看到tree就是二叉树,就要想到排序,就想要用到比较。

比较的两种方式:

一个是Comparable:覆盖compareTo方法;

一个是Comparator:覆盖compare方法。

LinkedHashSet,LinkedHashMap:这两个集合可以保证哈希表有存入顺序和取出顺序一致,保证哈希表有序。

 

集合什么时候用?

当存储的是一个元素时,就用Collection。当存储对象之间存在着映射关系时,就使用Map集合。

保证唯一,就用Set。不保证唯一,就用List。

 

Collections:它的出现给集合操作提供了更多的功能。这个类不需要创建对象,内部提供的都是静态方法。

 

Collection 和 Collections的区别:

Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。

Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。

Arrays:

用于操作数组对象的工具类,里面都是静态方法。

关于集合内部的方法,这里就不介绍了,API中都有。

十一:IO流:用于处理设备上的数据。

相关概念:

1.流:可以理解为数据的流动,就是数据流。IO流最终要以对象来体现,对象都存在IO包中。流的操作只要两种,读和写。

2.流也可以进行分类:1.输入流(读)和输出流(写)。2.因为处理的的数据不同,分为字符流(Reader  Writer)和字节流(InputStream  OutputStream)。

字节流:处理字节数据的流对象。计算机底层的数据都是二进制格式字节,所以字节流的数据可以是音频,图片,文字等计算机中可以储存的数据。

字符流:为什么要有字符流?字节流不是可以操作一切数据?因为字符每个国家都不一样,所以涉及到了字符编码问题,像如果我们的使用GBK的编码,却按照ISO8859-1去解码是有问题的,所以需要我们在获取文字字节数据的同时+制定的编码表才可以正确解析数据。因而将字节流和编码表封装为对象,就是字符流,只要操作字符数据,那么悠闲考虑使用字符流体系。

重要知识点:

1.close()方法和flush()的区别:

flush():将缓存区的数据刷到目的地中后,流可以继续使用。

close():将缓冲区数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源,这个动作在使用完毕流后一定要做。

2.FileWriter写入数据的细节:

windows中的换行符:\r\n 两个符号组成。  linux:\n。

在原数据上续写数据,只要在new 流对象的构造函数中传入新的参数true。

目录分割符: windows \\  /。

 

流的两种读取数据方法:1.不带缓冲区,每次读取一个(fr.read())。2.自定义缓冲区(fr.read(buff))

case  1:核心代码

[java] view plain copy

  1. FileReader fr = new FileReader("demo.txt");  
  2.   int ch = 0;  
  3.   while((ch=fr.read)!=-1){  
  4.   System.out,println((cahr)ch);   
  5.   }  
  6.   fr.close();  

case 2:

[java] view plain copy

  1. FileReader fr = new FileReader("demo.txt")  
  2. char[] buff = new char[1024]; //自定义的缓冲区  
  3. int len = 0;  
  4.    while((len=fr.read(buff))!=-1){  
  5.    System.out.println(buff,0,len);  
  6.   }  
  7. fr.close();  



 

IO流体系:

字符流:

Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。

     |---BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

        |---LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。

     |---InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

        |---FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。

     |---CharArrayReader:

     |---StringReader:

-------------------------------------------------

Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。

     |---BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

     |---OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

        |---FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。

     |---PrintWriter:

     |---CharArrayWriter:

     |---StringWriter:

---------------------------------

字节流:

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

     |--- FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。

     |--- FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。

        |--- BufferedInputStream:该类实现缓冲的输入流。

     |--- ObjectInputStream:

     |--- PipedInputStream:

-----------------------------------------------

OutputStream:此抽象类是表示输出字节流的所有类的超类。

     |--- FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。

     |--- FilterOutputStream:此类是过滤输出流的所有类的超类。

        |--- BufferedOutputStream:该类实现缓冲的输出流。

        |--- PrintStream:

        |--- DataOutputStream:

     |--- ObjectOutputStream:

     |--- PipedOutputStream:

--------------------------------

缓冲区是提高效率用的,给谁提高呢?

BufferedWriter:是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。明确要提高具体的流对象的效率。

 

[java] view plain copy

  1. FileWriter fw = new FileWriter("bufdemo.txt");  
  2. BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。  
  3. for(int x=0; x<4; x++){  
  4. bufw.write(x+"abc");  
  5. bufw.newLine(); //写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。  
  6. bufw.flush();//对缓冲区进行刷新,可以让数据到目的地中。  
  7. }  
  8. bufw.close();//关闭缓冲区,其实就是在关闭具体的流。  


 

 

-----------------------------

BufferedReader:

 

[java] view plain copy

  1. FileReader fr = new FileReader("bufdemo.txt");  
  2. BufferedReader bufr  = new BufferedReader(fr);  
  3. String line = null;  
  4. while((line=bufr.readLine())!=null){  //readLine方法返回的时候是不带换行符的。  
  5. System.out.println(line);  
  6. }  
  7. bufr.close();  


 

 

流一些总结:

流对象:其实很简单,就是读取和写入。但是因为功能的不同,流的体系中提供N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。

流的操作规律:

1,明确源和目的。

数据源:就是需要读取,可以使用两个体系:InputStream、Reader;

数据汇:就是需要写入,可以使用两个体系:OutputStream、Writer;

2,操作的数据是否是纯文本数据?

如果是:数据源:Reader

    数据汇:Writer 

如果不是:数据源:InputStream

      数据汇:OutputStream

3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?

明确操作的数据设备。

数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)

数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。

4,需要在基本操作上附加其他功能吗?比如缓冲。

如果需要就进行装饰。

 

转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。

转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。

 

发现转换流有一个子类就是操作文件的字符流对象:

InputStreamReader

|--FileReader

OutputStreamWriter

|--FileWrier

 

想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。

但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。

FileReader fr = new FileReader("a.txt");

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");

以上两句代码功能一致,

如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //因为简化。

如果需要制定码表,必须用转换流。

转换流 = 字节流+编码表。

转换流的子类File = 字节流 + 默认编码表。

 

转换流使用格式:转换流  fr =  new 转换流(包装的流,指定的编码集);

InputStreamReader fr = new InputStreamReaderfr (new FileInputStream("a.txt"),"GBK")

 

十二:网络编程。

相关概念:

1.逻辑端口:用于表示进程的逻辑地址,不同的进程的标识;有效进程:0~65535,其中0~1024是系统使用或者保留端口
2.物理端口:指的是主机或者其他设备上提供的外接接口。

Java中的IP对象 InetAddress.
Socket套接字,通信的端点:就是为网络服务提供的一种机制,通信两端都有Socket,网络通信其实就是端口Socket间的通信,数据在两个Socket之间通过IO传输

 

应用层的主要协议和方式有两张:UDP 和 TCP 两种.

1.UDP:面向数据包的传输,是面向无连接的方式.

 UDP传输:
 1.只要进行网络传输,必须需要Socket套接字。
 2.数据一定要封装到数据包中,数据包中包含IP地址,端口号,数据等信息。
 Java中封装操作UDP来进行网络通信的主要类是DatagramSocket对象以及DatagramPacket(数据包)。
 DatagramSocket:具备接受和发送功能,但进行传输时,需要明确发送端和接收端。
 DatagramPacket:数据包对象,用于操作数据包中的各种信息。
 
 UDP传输发送端的步骤:
 1.建立UDP的Socket服务,创建对象时如果没有声明端口,那么系统会自动分配给其一个未使用的端口号。
 2.明确要发送的数据。
 3.将数据封装到数据包对象中。
 4.用Socket的Send方法将数据包发送出去。
 5.关闭资源(必须记得做)
 
 下面给出一个例子作为参考:
[java] view plain copy

  1. class UdpSend{  
  2.    public static void main (String[] args){  
  3.       //1.使用DatagramSocket来建立UDP的Socket服务  
  4.       DatagramSocket ds = new DatagramSocket(8088);//指定发送端的端口8088,如果不指定自动默认分配  
  5.       //2.明确要发送的具体数据   
  6.       String context = "发送一段UDP信息。";  
  7.       byte[] buff = context.getBytes();  
  8.       //3.将数据封装到要发送的数据包中  
  9.       DatagramPacket dp = new DatapramPacket(buff,buff.length,InetAddress.getName("192.168.0.112"),10000);//要将该信息发往指定主机的10000端口上  
  10.       //4.使用Socket的send方法,将数据包发送出去。  
  11.       ds.send(dp);  
  12.       //5.关闭资源  
  13.       ds.close();  
  14.    }  
  15. }  



UDP传输接受端的步骤:
1.建立UDP的Socket服务,明确一个端口,作用在于,只有发送到这个端口的数据才是这个接受端可以接受处理的数据
2.创建数据包对象用于接受(存储)数据包。
3.利用Socket服务的接受方法将收到的数据存储到数据包中。
4.通过数据包对象获取数据包中的具体内容,如ip地址,端口,数据等。
5.关闭资源(必须做)


接受端的例子:
[java] view plain copy

  1. class UdpReceive{  
  2.   public static void main(String[] args){  
  3.     //1.使用DatagramSocket建立UDP的Socket服务。  
  4.     DatagramSocket ds = new DatagramSocket(10000);//设置端口,声明接受该端口的数据  
  5.     //2.创建数据包对象,创建接受存收到的数据。(需要先定义字节数组,数据包会将接受到的数据存入到字节数组中)  
  6.     byte[] buff = new byte[1024];  
  7.     DatagramPacket dp = new DatagramPacket(buff,buff.length);  
  8.     //3.利用Socket服务,接受发送过来的数据包  
  9.     ds.receive(dp);//该方法是阻塞式方法,没有监听到有数据发送过来的时候,会一直等待。  
  10.     //4.通过数据包对象的方法获取数据包中信息  
  11.     String ip = dp.getAddress.getHostAddress();  
  12.     int port = dp.getPort();  
  13.     String context = new String(dp.getData(),0,dp.length);  
  14.     System.out.println("ip:"+ip+" port:"+port+" context:"+context);  
  15.     //5.关闭资源  
  16.     ds.close();  
  17.   }  
  18. }  

TCP传输:两个端点建立连接后会有一个传输数据的通道,这通道成为流,而且是建立在网络基础之上的流,称之为Socket流,该流中既有读取,也有写入。

相关概念:TCP中的两个端点需要严格区分:一个是服务端,一个是客户端。

客户端:对应的对象,Socket

服务端:对应的对象,ServerSocket

 

TCP客户端:

1.建立TCPde Socket服务,最好明确具体的地址和端口,这个对象在创建时,就已经可以对指定ip和端口进行连接(三次握手)。

2.如果连接成功,就意味通道建立了,Socket流已经产生了。只要获取到Socket流中的读取流和写入流即可,只要通getInputStream和getOutputStream就可以获取到两个流对象。

3关闭资源。

 

case 1 :

[java] view plain copy

  1. class TcpClient{  
  2.     public static void main(String[] args){  
  3.          Socket s = new Socket("192.168.1.112",10002);  
  4.          OutputStream out = s.getOutputStream();               //获取了Socket流中的输出流对象。  
  5.          out.write("TCP连接。。。".getBytes());  
  6.          s.close();  
  7.    }  
  8. }  

TCP服务端:

1.创建Socket服务,并监听一个指定的端口。

2.服务端为了客户端提供服务,获取客户端的内容,可以通过accept方法获取连接过来的客户端对象。

3.可以通过获取到的Socket对象中的Socket流和具体的客户端进行通讯。

4.如果通讯结束,关闭资源。注意:要先关客户端,再关服务端。

case 2:

 

[java] view plain copy

  1. class TcpServer{  
  2.      public static void main(String[] args) throws Exception{  
  3.             ServerSocket ss = new ServerSocket(10002);    //建立服务端的Socket服务  
  4.             Socket s = ss.accept();      //获取客户端对象  
  5.             String ip = s.getInetAddress().getHostAddress();  
  6.             System.out.println(ip+“.....connection”);  
  7.             //可以通过获取到的Socket对象中的Socket流和具体的客户端进行通讯  
  8.             InputStream in = s.getInputStream();     //读取客户端的数据,使用客户端对象的Socket读取流  
  9.             byte[] buff = new byte[1024];  
  10.             int  len =  in,read(buff);  
  11.             String text = new String(buff,0,len);  
  12.             System.out.println(text);  
  13.             //关闭资源,注意一定是先关客户端,再关闭服务端  
  14.             s.close();  
  15.             ss.close();  
  16.      }  
  17. }  

 

网络编程中的URLEncoder和URLDecoder.

URLEncoder类的encode()静态方法:是将一个普通的字符串转化为一个百分号编码格式字符串。

URLDecoder类的decode()静态方法:是将百分号编码格式字符串转化为一个普通的字符串。

URL与URLConnection对象:前者是表示应用程序和URL之间的通信连接,后者表示与URL之间的HTTP连接。程序可以通过URLConnection实例向该URL发生请求,读取URL引用的资源。

 

 

 

 

未完待续。。。。。持续更新

转载于:https://my.oschina.net/u/238082/blog/1613473

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值