SE总结

来源:互联网 发布:Windows ftp 服务 编辑:程序博客网 时间:2024/05/01 08:04

JVM(Java Virtual Machine)

JVM是Java具有平台独立性的关键因素。Java程序被编译后不是生成能在硬件平台上可执行的代码,而是生成了一个“中间码”(后缀为.class的字节码文件)。不同的硬件平台,会装有不同JVM,由JVM来负责把“中间码”翻译成硬件平台能执行的代码。(即,Java程序的运行环境为,Java—JRE/JVM—操作系统—硬件)
JVM加载class文件的原理:
class只有被加载到JVM中后才能运行,当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规则加载到内存中,并组织成为一个完整的Java应用程序。这个过程是由类加载器来完成的。类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中。
类的加载方式有两种:隐式加载,指程序通过new等方式创建对象,会隐式地调用类的加载器把对应的类加载到JVM中;显示加载,指直接调用class.forName()方法来把所需要的类加载到JVM中。

对象初始化

JVM的ClassLoader分三层,分别为 Bootstrap ClassLoader类加载器加载JRE/lib,Extension ClassLoader加载 JRE/lib/ext,App ClassLoade加载ClassPath/,他们不是类继承的父子关系,是逻辑上的上下级关系。
Java程序启动后,JVM启动,运行Bootstrap ClassLoader,该ClassLoader加载Java核心API(Extension ClassLoader和AppClassLoader也在此时加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class。
JVM默认是父类委托加载机制,即首先判断缓存是否有已加载的类,如果缓存没有,但存在父加载器,则让父加载器加载,如果不存在父加载器,则让Bootstrap ClassLoader去加载,如果父类加载失败,则调用本地的findClass方法去加载。
使用这种委托方式的原因:
1.因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2.考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代Java核心API中定义类型,这样会存在非常大的安全隐患,而父类委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。
自定义加载类需要继承ClassLoader类重写findClass()方法。
一些重要方法:
1.loadClass()
是ClassLoader的入口,其定义如下:
Class loadClass(String name , boolean resolve);
name是指JVM需要的类的名称,resolve是告诉方法是否需要解析类,在准备执行类之前,应考虑类解析。
2.defineClass()
接受由原始字节组成的数组,并把它转换成Class对象,被final修饰,所以不能被覆盖。
3.findClass()
loadClass默认实现调用这个方法,这个方法定义了ClassLoader查找class的方式。
4.resolveClass()
链接指定的类(方法参数中指定),类加载器使用此方法来链接类。
Java程序初始化的三原则:
1.静态对象优先于非静态(静态对象只初始化一次,非静态对象可能初始化多次)
2.父类优于子类
3.按成员变量顺序
创建对象
对象的所有成员变量首先要进行初始化,只有当所有类成员(即使变量定义散布于方法中,它们依然在任何方法被调用之前初始化)完成初始化后,才会调用对象所在类的构造函数,从而创建对象。构造函数在对象实例化时会被自动调用且只运行一次,不能被程序编写者调用,必须要由系统调用;而普通方法是在程序执行它时调用,并且可以调用多次。
创建对象的方式
1.new()语句实例化:new创建对象时静态加载类,在编译时就需要加载所有可能用到的类;
2.反射机制;3.通过clone()方法;4.通过反序列化

内存管理

GC(Garbage Collection)

Java的内存管理实际就是对象的管理,其中包括对象的分配和释放。对于程序员来说,分配对象使用new关键字,释放对象时只是将对象赋值为null,让程序员不能够再访问到这个对象,该对象被称为“不可达”GC的主要作用是回收程序中“不可达”对象的内存。
具体可分为三项任务:分配内存、确保被引用对象的内存不被错误地回收以及回收不再被引用的对象的内存空间。
finalize是Object类中的定义的方法,因此所有的类都继承了它,子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。Java技术允许使用finalize()方法在GC从内存中清除出去之前做必要的清理工作。这个方法是由GC在确定这个对象没有被引用时(GC删除对象之前)对这个对象的调用的。所以,finalize()方法并不可靠,它仍然需要GC判断对象是否“可达”,从而降低GC的运行性能。

内存泄漏

也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。
内存泄露的情况主要有两种:一是在堆中申请的空间没有被释放;二是对象已经不再被使用,但仍然还在内存中保留着。
GC的引用可以解决第一种情况,但是第二种情况无法保证不再使用的对象会被释放。所以,Java中内存泄露主要是指第二种情况,主要有以下几个方面:
1.静态集合,由于他们的生命周期与程序一致,那么容器中的对象在程序结束之前将不能被释放,从而造成内存泄露。
2.各种连接,对数据库,IO等进行操作时,首先要建立连接,当不再使用时需要调用close()方法,来释放连接。只有当连接关闭以后,GC才会回收对应的对象。
3.监听器,当应用中的监听器在释放对象的同时没能及时删除监听器,则会造成内存泄露。
4.变量不合理的作用域。当变量作用范围大于实用范围,很可能造成内存泄露。
5.单例模式,当单例模式引用一个对象时,由于单例模式以静态变量的方式存储,因此它整个JVM生命周期都存在,所以导致对象不能被回收。

Java中的堆栈

在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配 。
当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间, 该内存空间可以立即被另作他用。
堆内存用来存放由 new创建的对象和数组。
在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
Java语言不允许直接访问堆内存中的数据,因此需要引用变量来访问堆内存的数据。

Java中引用的用法

在堆中产生了一个数组或对象后,可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。 引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。

数据类型与运算符

原码
符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
[+1]原 = 0000 0001
[-1]原 = 1000 0001
第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:
[1111 1111 , 0111 1111]即[-127 , 127]
反码
正数的反码是其本身
负数的反码是在其原码的基础上,符号位不变,其余各个位取反。
补码
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
这里写图片描述
在计算机中,运算基础都来源于简单数字,包括Java,即使是包装后的对象,在真正计算的时候也是通过内在的数字来完成的。
这里写图片描述

字符串及其操作

4种操作字符串的类:
Character用于操作单个字符;
String是不可变类(即String对象一旦被创建,其值不能改变)且JVM中存在一个字符串池,其中只能有一个同名的对象;
StringBuffer是可变类,对象创建后依然可以对其值进行修改;当一个字符串经常被修改时,使用StringBuffer可以是程序提高效率;
StringTokenizer分割字符串工具类
1.hasMoreTokens()方法:测试此tokenizer字符串是否还有更多的可用标记;
2.nextToken()方法:返回此tokenizer的下一个标记;
StringBuilder与StringBuilder的方法相同,只不过是同步的,而StringBuilder是异步的。
所以,如果要操作的数据量较小,应该优先使用String类;如果在单线程下操作大量数据,应优先使用StringBuilder类;如果在多线程下操作大量数据,应该用StringBuffer.

关键字

final
final的用法:(不可修改)
<1>修饰类的时候该类不能被继承。例如:String,String默认final修饰
<2>修饰方法的时候该方法不能被重写。
<3>修饰字段的时候该字段时常量。
修饰字段时的赋值方式有直接赋值和构造方法赋值两种形式

static
static内部函数(又称静态函数)如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。静态方法在使用时只能调用静态方法或静态变量,非静态方法中既可调用非静态的方法或非静态变量,也可以调用静态的方法或静态变量
1、static修饰变量时称为静态变量,也叫类变量。修饰字段时,属于该类的对象所共有,在内存中只有一份
2、static修饰方法时称为静态方法,也叫类方法。修饰方法时,属于该类的对象所共有,在内存中只有一份
使用规则:有两种使用方式
<1>实例化后通过 引用名.属性名(方法名)来调用
<2>直接通过 类名.属性名(方法名)来调用
NOTE:建议用第<2>中方式。

switch

switch(表达式){case 常量表达式1:语句1;case 常量表达式2:语句2;default:语句;}

switch的用法是判断case后面的表达式和switch后面的表达式是否相匹配,一旦case匹配,就会顺序执行后面的程序代码,而不管后面的case是否匹配,直到遇见break。
1.default就是如果没有符合的case就执行它,default并不是必须的。
2.case后的语句可以不用大括号,case 后面必须是常量表达式constant expressions,错误表示如: case x。
3.switch语句的判断条件可以接受int,byte,char,short,enum不能接受其他类型。
4.一旦case匹配,就会顺序执行后面的程序代码,而不管后面的case是否匹配,直到遇见break,利用这一特性可以让好几个case执行统一语句。

数组

Java中的数组有自己的属性以及方法,从这个角度来讲,数组是对象。
初始化:
type []变量名 = new type [数组个数]
为数组分配内存空间时为各个元素指定初始值,比如int类型的初始值(即不赋值时)为0,double类型的初始值为0.0,布尔类型的初始值为false等。
数组下标从0开始
数组默认附带length属性,其是为了获得数组的长度,而在JAVA中的length()方法是针对字符串计算长度的(String s =”abc”; s.length() )。
第一个运行时异常:当数组下标越界在执行时会发生,但不影响编译 ArrayIndexOutOfBoundsException 数组越界异常

“==”、equals、HashCode
“==”比较的是内存中所存储的数值是否相同,即要比较的是两个基本数据类型的变量,可以直接用“==”;如果比较的是引用类型的变量,则涉及到两块内存,对象本身、引用,这时候比较两个变量是否指向同一个对象可以使用“==”,要比较对象的内容则无法实现。
equals为Object提供的方法,定义使用“==”比较对象,与“==”不同的是,equals可以被覆盖,让它比较的不是引用(且此方法是希望子类去重写,实现对比值,等等功能)。
HashCode是返回对象在内存中地址转换成的一个int值(这个数字具有一定的标识对象的意义,但绝不等价于地址)。一般在覆盖equals方法时,也要覆盖HashCode方法。(equals推出相等,则HashCode一定相等;HashCode相等,则equals不一定相等)

继承和组合

通过extends实现
继承了父类的属性跟方法,即减少代码的重复使用,方便程序的维护与扩展。
在生成子类的实例对象时,系统将默认调用父类无参数的构造方法,即要注意在父类中,写带参的构造方法时,也必须写无参的,否则将造成不能实例化。若子类没有重写构造方法,根据java规则,系统会默认的添加一个无参的构造方法,且该方法第一句是super()。
大部分情况是 super.超类方法()的形式,就是super后有个点,然后接上超类的方法,这样就是子类调用超类的方法。
super可以调用父类的某个构造方法,this可以调用本类中另一种构造方法,但这两者在调用构造方法时应该为构造方法的第一条语句。
this.就是调用当前对象即成员变量
通常来讲,继承主要是为了让我们根据自己的实现来覆盖重写父类的实现细节,父类的实现对子类是可见的。(一般称为白盒复用)
组合,(即对象持有)实现抽象类或者接口,然而整体类和部分类之间不会去关心各自的实现细节,即它们的实现细节不可见。(故称黑盒复用)
继承是在编译时刻静态定义的,即是静态复用,在编译后子类和父类的关系就已经确定了。而组合这是运用于复杂的设计,它们之间的关系是在运行时候才确定的,即在对对象没有创建运行前,整体类是不会知道自己将持有特定接口下的那个实现类。在扩展方面组合比集成更具有广泛性。
继承中父类定义了子类的部分实现,而子类中又会重写这些实现,修改父类的实现,设计模式中认为这是一种破坏了父类的封装性的表现。这个结构导致结果是父类实现的任何变化,必然导致子类的改变。然而组合这不会出现这种现象。
对象的组合还有一个优点就是有助于保持每个类被封装,并被集中在单个任务上(类设计的单一原则)。这样类的层次结构不会扩大,一般不会出现不可控的庞然大类。而类的继承就可能出来这些问题,所以一般编码规范都要求类的层次结构不要超过3层。组合是大型系统软件实现即插即用时的首选方式。

  • 继承
    • 优点
      • 不破坏封装,整体类和局部类松耦合,彼此相对独立
      • 具有较好的可拓展性
      • 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象
      • 整体类可以对局部类进行包装,封装局部类的接口,提供新的接口
    • 缺点
      • 整体类不能自动获得和局部类同样的接口
      • 创建整体类的对象时,需要创建所有局部类的对象
  • 组合
    • 缺点
      • 破坏封装,子类和父类紧密耦合,子类依赖父类的实现,子类缺乏独立性
      • 支持扩展,但是往往以增加系统结构的复杂度为代价
      • 不支持动态继承。在运行时,子类无法选择不同的父类
      • 子类不能改变的接口
    • 优点
      • 子类能自动继承父类的接口
      • 创建子类的对象时,无需创建父类的对象

抽象类和接口

  • 抽象类

    • 可以有抽象方法和普通方法, 可以有静态代码块和静态方法
    • 抽象类类似于一种模板
    • 一个类只能继承一个抽象类
    • 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public
  • 接口

    • 只能有抽象方法,不能含有静态代码块以及静态方法
    • 接口相当于标准,更方便解耦
    • 接口与类之间是(implements)关系,类可实现多接口,接口与接口之间是继承关系

在JDK7之前的接口中,属性都为静态常量,方法都为抽象方法(即接口中的方法必须是public abstract 的,不可以私有,其并不需要显示的写出来;接口中的属性都是public static final类型的,必须赋初始值。)。(JDK7之后只是用default方法)。
在设计层面,抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类。当需要添加新的方法时,可以直接在抽象类中添加具体的实现,子类可以不进行变更而对于接口则不行;接口是根据行为而言(接口分离原则的核心思想:不应该强迫客户程序依赖它们不需要使用的方法)。即一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口当中。所以,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

基础类

Math类中包含了很多跟数学相关的函数方法

public class MathDemo {    public static void main(String args[]){        /**         * abs求绝对值         */        System.out.println(Math.abs(-10.4));    //10.4        System.out.println(Math.abs(10.1));     //10.1        /**         * ceil天花板的意思,就是返回大的值,注意一些特殊值         */        System.out.println(Math.ceil(-10.1));   //-10.0        System.out.println(Math.ceil(10.7));    //11.0        System.out.println(Math.ceil(-0.7));    //-0.0        System.out.println(Math.ceil(0.0));     //0.0        System.out.println(Math.ceil(-0.0));    //-0.0        /**         * floor地板的意思,就是返回小的值         */        System.out.println(Math.floor(-10.1));  //-11.0        System.out.println(Math.floor(10.7));   //10.0        System.out.println(Math.floor(-0.7));   //-1.0        System.out.println(Math.floor(0.0));    //0.0        System.out.println(Math.floor(-0.0));   //-0.0        /**         * max 两个中返回大的值,min和它相反,就不举例了         */        System.out.println(Math.max(-10.1, -10));   //-10.0        System.out.println(Math.max(10.7, 10));     //10.7        System.out.println(Math.max(0.0, -0.0));    //0.0        /**         * random 取得一个大于或者等于0.0小于不等于1.0的随机数         */        System.out.println(Math.random());  //0.08417657924317234        System.out.println(Math.random());  //0.43527904004403717        /**         * rint 四舍五入,返回double值         * 注意.5的时候会取偶数         */        System.out.println(Math.rint(10.1));    //10.0        System.out.println(Math.rint(10.7));    //11.0        System.out.println(Math.rint(11.5));    //12.0        System.out.println(Math.rint(10.5));    //10.0        System.out.println(Math.rint(10.51));   //11.0        System.out.println(Math.rint(-10.5));   //-10.0        System.out.println(Math.rint(-11.5));   //-12.0        System.out.println(Math.rint(-10.51));  //-11.0        System.out.println(Math.rint(-10.6));   //-11.0        System.out.println(Math.rint(-10.2));   //-10.0        /**         * round 四舍五入,float时返回int值,double时返回long值         */        System.out.println(Math.round(10.1));   //10        System.out.println(Math.round(10.7));   //11        System.out.println(Math.round(10.5));   //11        System.out.println(Math.round(10.51));  //11        System.out.println(Math.round(-10.5));  //-10        System.out.println(Math.round(-10.51)); //-11        System.out.println(Math.round(-10.6));  //-11        System.out.println(Math.round(-10.2));  //-10    }}

反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
类是java.lang.Class 的实例对象
Class实例对象的三种方法:
Class c1 = 类.class; 任何一个类都有一个隐含的静态成员变量class,通过.class方法得到一个类类型Class

异常

检查异常(checked exception):java编译器强制程序去捕获检查异常。即,把这些异常代码放到try中,把对异常处理的代码放到catch中。这类异常有两种情况1.它的发生并不会导致程序出错,进行处理后可以继续后续操作;2.程序依赖于不可靠的外部条件(例如:系统IO)
运行时异常(runtime exception):编译器没有强制去捕获此类异常。当出现这种异常时,会由JVM处理,常见的运行时异常有,NullPointException、ClassCastException、ArrayIndexOutBoundsException、ArrayStoreException(数组存储异常)、BufferOverflowExceptin(缓冲区溢出异常)、ArithMeticException(算术异常)等。
throw和throws区别:1.throw用于程序员自行产生并抛出异常;throws用于声明该方法内抛出异常;2.throw用于方法内部;throws为方法列表后面,不能单独使用;3.throw抛出一个异常对象且只能为一个;throws后跟异常类且可抛出多个。
try-catch语句三种情况1.try中正常则catch语句被忽略;2.try中发生异常,catch声明异常匹配则执行catch;3.try中发生异常,catch不匹配,直接跳出方法。
try-catch-finally语句二种情况try中正常无论catch声明匹配不匹配finally都确保一段代码都会执行。(finally代码一定会执行吗?1.当try,catch中有return语句时,finally会在return之前执行;当finally中有return语句,它也会覆盖其他函数中的return。2.当try块执行之前就出现异常,程序直接停止,则finally不会执行;当try块被程序强制退出也不会执行finally,例如调用system.exit()方法。)
多重catch要按照从小到大的顺序,子类在前,最后一个一般都是Exception。(因为异常处理用到了多态的概念,如果先捕获了基类,那么子类的代码将永远不会被执行。)

IO

这里写图片描述
序列化(实现Serializable接口)
它是一种将对象以一连串的字节描述的过程,用于解决在对对象流进行读写操作时所引发的问题。
1、如果一个类能被序列化,那么它的子类也能被序列化
2、由于static代表类成员,transient代表临时数据,因此被申明为这两种类型的数据成员是不能被序列化的。

socket

Socket可以用来实现不同虚拟机或不同计算机之间的通信。
Socket的生命周期可以分为三个阶段:
1.Server端Listen指定的某个端口(建议使用大于1024)是否有连接请求,(即创建ServerSocket的对象)
2.Client端向Server端发送Connect请求,(创建Socket对象向host主机,相同端口,发起请求)
3.Server端向Client端发回Accept消息,(即创建Socket对象,利用ServerSocket类的accept()方法接收消息)。

服务器端

public class Server {   public static void main(String args[]) throws IOException {      //为了简单起见,所有的异常信息都往外抛      int port = 8899;      //定义一个ServerSocket监听在端口8899上      ServerSocket server = new ServerSocket(port);      //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的      Socket socket = server.accept();      //跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。      Reader reader = new InputStreamReader(socket.getInputStream());      char chars[] = new char[64];      int line;      StringBuilder sb = new StringBuilder();      while ((line=reader.read(chars)) != -1) {         sb.append(new String(chars, 0, len));      }      System.out.println("from client: " + sb);      reader.close();      socket.close();      server.close();   }}

客户端

public class Client {   public static void main(String args[]) throws Exception {      //为了简单起见,所有的异常都直接往外抛      String host = "127.0.0.1";  //要连接的服务端IP地址      int port = 8899;   //要连接的服务端对应的监听端口      //与服务端建立连接      Socket client = new Socket(host, port);      //建立连接后就可以往服务端写数据了      Writer writer = new OutputStreamWriter(client.getOutputStream());      writer.write("Hello Server.");      writer.flush();//写完后要记得flush      writer.close();      client.close();   }}

socket详解博客

Collection

Java Collections框架中包含了大量集合接口及其实现类,由于接口不能实例化(例如:List、Set、Map),所以在创建集合的时候不能直接new(),只能创建实现其类(例如:ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap)的对象。
Collection是整个集合框架的基础,它里面储存一组对象,表示不同类型的Collections,它的作用只是提供维护一组对象的基本接口。List、Set、Stack、Queue都继承自Collection接口。Collections是针对集合的一个包装类,它提供一系列操作,相当于一个工具类,服务于Collection框架。
List接口:有序的Collection,它按对象的进入顺序保存对象,所以它能对列表中的每一个元素的插入和删除位置进行精确的控制。存储一组不唯一(可重复),有序(添加顺序)的对象。以下为它的实现类:
1.ArrayList和Vector都会在内存中开辟一块连续的空间,由于数据存储是连续的,因此,他们支持用序号(下标)来访问元素,同时索引数据的速度较快,但是在插入元素时需要移动容器中的元素,所以对数据的插入较慢。
ArrayList和Vector都有一个初始化的容量的大小,当里面存储的元素超过这个大小时就炫耀动态地扩充他们的存储空间。为了提高程序的效率,每次扩充容量,不是简单地扩充一个单元,而是一次多增加几个存储单元。Vector默认为原来2倍(大小可以设置),ArrayList默认原来1.5倍(没有扩充方法)。
ArrayList和Vector的区别:Vector是同步的,同步意味着安全。用Enumeration迭代器迭代,不能以Iterator迭代,因为如果数据量特别大时,迭代会快速失败。ArrayList是异步的、不安全的。
2.LinkedList是采用双向链表来实现的,对数据的索引需要从链表头开始遍历,因此用于随机访问则效率比较低,但是插入元素不需要移动数据,所以插入效率高。同时LinkedList是非线程安全的。
实际运用:当对数据操作主要为索引或者只在末端增删元素时,使用ArrayList和Vector效率高;当对数据操作为指定位置的插入删除时,使用LinkedList效率高;当在多线程中选用Vector较为安全。

Set接口:主要特点元素不能重复,因此存入Set的元素都必须定义equals()方法来确保对象的唯一性。存储一组唯一,无序(添加顺序)的对象。
1.HasHSet无序的。
2.TreeSet同时实现了SortedSet接口,因此它是有序的。
以上List和Set接口都允许添加null值

Map接口:用来保存键值对,其中值可以重复,键唯一。提供一组键值对象,提供key到value的映射(一一对应)。不能直接使用迭代器。以下为它的实现类
1.HashMap(通常使用多态实现更好)此实现提供所有可选的映射操作,并允许使用 null 值和 null 键,但是需要注意,最多只允许一条键为null,不允许多条值为null。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。一个方便的方法就是利用Collections类的静态的synchronizedMap()方法,它创建一个线程安全的Map对象,并把它作为一个封装的对象来返回。
2.Hashtable是同步的,同步意味着效率低,但是安全。java se中常用类中唯一一个没有按照命名规范定义的一个类名,其中Hashtable中的”t”是小写,而不是大写。
Hashtable使用Enumeration,HashMap使用Iterator。
3.SortedMap进一步提供关于键的总体排序的 Map。和SortedSet类似,该映射是根据其键的自然顺序进行排序的,或者根据通常在创建有序映射时提供的 Comparator 进行排序。
这里写图片描述
泛型的本质就是参数化类型(可保护集合使用过程中安全),参数化类型重要性在于,允许创建一些类、接口和方法,其所操作的数据类型被指定为参数。例如我们可以使用泛型创建一个类,在这个类中可以自动使用不同类型的数据。Java 泛型的参数只可以代表类,不能代表个别对象。注意泛型在map中需要添加键值两个参数类型。

线程

进程:应用程序执行的实例。动态的、并发的、独立的。
线程:进程内部的一个执行单元,它是程序中一个单一的顺序控制流程。至少有一个父进程;可以有自己的堆栈、程序计数器和局部变量(局部变量与成员变量在使用多个线程操作时的不同,成员变量的话只能有其中一个线程访问);与父进程的其他线程共享资源;独立运行,采用抢占方式;一个线程可以创建删除另外一个;同一个进程当中的多个线程可并发执行;线程的调度管理是由进程来完成的。
这里写图片描述
创建线程一:1、继承Java.lang.Thread类;2、重写run()方法;3、调用start()方法
线程执行调用run()方法,执行run()方法时要使用start()方法,start()方法会使系统创建一个新的线程,由此线程调用run()方法(Thread类的run()方法是一个空方法)
创建线程二(通常): 1、实现Runnable接口;2、重写run()方法;3、new Thread(Runnable)的多态方式创建线程对象;4、调用Thread的start()的方法(所以本质还是通过Thread对象的API来控制线程)
并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥。

优缺点
- 继承Thread类有一个缺点就是单继承,而实现Runnable接口则弥补了它的缺点,可以实现多继承
- 继承Thread类必须如果产生Runnable实例对象,就必须产生多个Runnable实例对象,然后再用Thread产生多个线程;而实现Runnable接口,只需要建立一个实现这个类的实例,然后用这一个实例对象产生多个线程。即实现了资源的共享性
并行:单处理器中,进程交替执行;多处理器中,可同时运行多个进程(跟CPU数相关)。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。

同步:确保资源在单位时间内只运行一个线程,就是前一个线程的锁必须被释放,后一个线程才能进入临界区,当第一个线程锁没有释放时其他线程必须等待。
异步:每个线程都包含了运行自身所需的方法或数据,因此,在进行输入输出处理时,不必关心其他线程的状态或行为,也不必等到输入输出处理完毕才返回。

多线程同步方法:
1. synchronized关键字:
1). synchronized方法,在方法的声明前加入此关键字
2). synchronized代码块
2. wait()和notify()方法
join()方法作用是让调用该方法的线程在执行完run()方法后,再执行join()后面的代码。即,让两个线程合并,实现同步功能。
3. Lock类
1)lock()以阻塞的方法获取锁,也就是说如果获得了锁,立即返回;如果别的线程持有锁,等待,直到获得锁返回。
2)tryLock()以非阻塞的方法获取锁,只是尝试性获取一下,如果获取到立即返回true,否则,立即返回false
3)tryLock(long timeout, TimeUnit unit)类似第二种方法,可以设置等待时间,超时,则返回false
4)lockInterruptiby()如果获取了锁,立即返回;如果没有,当前线程休眠直到获取,或者当前线程被别的线程中断(收到InterruptedException)与lock()方法的区别在于lock()获取不到锁会一直处于阻塞状态,并且会忽略interrupt()方法,即不会抛出异常。
wait()和sleep()区别:
sleep()是Thread类的静态方法且必须捕获异常,由线程自身控制,可以自动“苏醒”,所以sleep()不会释放“锁标志”容易死锁;
wait()是Object类方法,这个方法只能用在同步代码块中且它使当前线程等待直到其他线程调用notify()方法,所以wait()涉及到线程间的通信。
sleep()和yield()区别:
1)sleep()给其他线程运行机会时不考虑线程优先级,而yield()方法会给相同优先级或更高优先级线程以机会。
2)执行sleep()进入阻塞状态,一定不会再被执行,而yield()只是让其回到可执行状态,可能被再执行
3)sleep()抛出异常且比yield()有更好的移植性
守护线程:当用户线程已经全部退出运行,只剩下守护线程存在,JVM也就退出了。因为当所有非守护线程结束了,没有了被守护者,那么守护线程也就没有工作可做了,也就没有继续运行的必要了,程序将终止,同时结束所有守护线程。(用户可以自己设置守护线程,在调用start()方法之前调用setDaemon(true)方法,若设置为false,则表示是用户进程模式,其中如果一个守护线程产生了其他线程,那么这些新线程还是守护线程。守护线程的一个经典例子就是GC,只要JVM启动,它始终在运行,实时监控系统中可以被回收的资源)

0 0
原创粉丝点击