java常用类

集合框架

概述

数组是按照位置存储的,删除某个数据后,该位置空闲,重复利用比较麻烦,需要移动数据。有没有自动扩展长度,删除元素后数据会自动的释放空间呢?集合正是这样的数据结构

集合:长度可以自动的扩展,存储的类型可以不限定(这点不太安全)。

 1)集合长度自动扩展,

    2)集合可以使用泛型指定存储类型,必须是引用类型,不能存储基本类型,如:int,

    3)集合的API比较丰富,比如add,remove,contains方法,

    4)集合接口有丰富的数据结构实现,

JAVA的集合框架中有两个基本的集合接口,一个是Collection,还有一个是Map,

        Collection继承 Iterable接口,这个接口允许对象成为 "foreach" 语句的目标;

        Collection接口规范了实现该接口的集合可以存储不唯一、无序的对象,它有几个比较实用的子接口:有序列表List集合、不包含重复元素的Set集合和队列Queue等。

注意:这里的有序和无序并不是指元素值的大小顺序关系,而是指插入顺序,对于List来说,存入数据的顺序和取出的顺序是一致的,所以List里有位置索引信息,而对于Set来说就不是这样了,访问到的元素并非是插入的顺序。

 

List:有序列表,存储空间是有下标的,可以按位置存取。

Set:散列集合,存储空间没有下标。

前两者都是Collection的子接口

Map:键值对,根据键值K取V,键值K是唯一的

循环

普通for循环使用下标访问元素,对于数组或者以数组为基础实现的数组列表等的随机访问会更快一些;

增强for循环书写上简洁方便,循环中实际上使用了迭代器进行迭代,对于没有位置索引的集合也能进行遍历,特别是对链式列表的访问速度更快。

JAVA的集合是可以存储多个数据类型的,Collection coll=new ArrayList();在coll中可以存储除了基本数据类型(装箱为包装类存储)的一切对象。就是说可以存储Integer String Employee,所以在读取的时候是无法知道其准确的类型是什么的。

需要强制类型转换,除非事先知道类型否则会出现类型转换异常,这样程序的安全性就降低。

泛型

使用泛型,就是在编译阶段限定其存储的类型是什么,这样就不能随便存储其他类型了,读取时也不需要强制类型转换了。

集合中使用泛型来指定参数类型:

  1)可以让代码更安全健壮

  2)不需使用强制类型转换

迭代器

注意在读取前一定要进行判断是否可以迭代(hasNext()),然后进行读取下一个(next()),

调用集合对象的iterator()方法可以获取该集合上的迭代器对象,然后通过该迭代器就可以迭代整个集合对象了,但是在使用时有些需要注意的地方:

1. 不用重复调用next

使用迭代器的next()方法可以返回下一个被迭代的元素,在使用前需要通过hasNext()来判断是否有可迭代元素,要注意,一次hasNext()判断有可迭代元素后,也只能有一个next()来取,

2. 在迭代过程中不要直接对集合进行结构上的修改,即不要在集合上进行增加元素或删除元素的操作。

原因在于在迭代器的实现中,next()方法内部会先调用一个方法来检查修改的版本号:

而对集合的add和remove都会修改这个modCount值,导致比较结果不一致而抛出异常

如果要在迭代过程中来删除元素,那么就使用迭代器的remove方法,

容器接口 

list

List接口是有序的列表,与父类 Collection比包含了Collection所有的方法,又扩展了能精确地控制每个位置上的元素的方法。

    如:

        list.get(i)读取第i位置的元素;

        add(int index, E element)指定位置添加元素;

        remove(int index)移除指定位置的元素;

        set(int index, E element)替换指定索引位置的元素;

        subList(int fromIndex, int toIndex)截取子列表。

List常用的实现类有ArrayList、Vector和LinkedList三个,都实现了List接口,所以它们的操作方法都是相同的,区别在于它们在底层上的实现方式不一样,ArrayList和Vector都是基于数组来存储元素的,LinkedList是基于链表方式存储元素的。

ArrayList:数组实现方式,随机查询速度快,增删速度慢;

LinkedList:链表实现方式,增删速度块,随机查询速度慢;

Vector:和ArrayList类似,不过多线程是线程同步的,保证了数据安全。

除了这几个类之外还有AttributeList、RoleList、Stack等其他的列表实现类

ListIterator

Set 

Set接口的实现类HashSet,HashSet是一个散列的集合,数据会按照散列值(HashCode)存储的,两个hello的散列值相同,在插入时会进行比较判断,结果第二插入的hello发现该哈希值相同就插入失败,所以看到的就是只有一个hello在集合中了。

Set集合的特征是:

    Set属于Collection的子接口,所以拥有Collection的所有的方法;

    Set没有位置编号,没有像List那样的按照编号进行操作的方法。

Set接口也有很多的实现类,常用的实现类有两种:

1. HashSet:散列集合里存储的对象通过equals和hashCode方法进行比较,因此自定义类对象必须重写这两个方法(String等类已经重写过了,同学们可以看下它们的源码)

2、TreeSet:树集集合通过红黑树(一种特殊的二叉树)结构来对元素进行排序(默认是自然顺序排序),排序比较如果相同那么就会排除重复元素,来保证Set的元素唯一特性 ,因此如果是使用TreeSet存储自定义类对象,那么有两种方式:a)自定义类实现Comparable接口,重写compareTo方法(后续会讲解排序接口) b)构造TreeSet时给定一个对自定义类比较的比较器对象

Map

通过对Map的常用操作,我们可以取出Map中的所有key的集合,那么对key遍历再取出对应value就实现了对Map的遍历,所以就是将Map的遍历转换为了Set遍历

在Map接口中有一个内部类,叫做Entry,代表了一个映射项,该项就包含了一个键-值对,对这个Entry可以调用getKey和getValue获取其中的键和值,我们可以获取Map集合的Entry的集合再进行遍历,代码如下:

 

Map(映射)是一个可以根据键值进行存储的,它的一个 Key 对应的是一个存储的位置,所以Key值是唯一的,根据Key值可以获取到对应的存储的Value。

这种存储的集合我们称为 “键-值” Map。

  1)它不是集合Collection的子类;

  2) 它的键值是唯一的,根据键值可以取出值;

  3) 根据值无法直接取出Key。

注意泛型中不能使用基本类型<double>,只能使用包装类型<Double>

 

Map(键值对)集合常见的操作:

按照键值K存储:map.put(K,V)

按照键值K读取:map.get(K);

是否包含键值 map.containsKey(Object key)

是否包含Value值 map.containsValue(Object value)

取出所有的K值 Set<K> map.keySet() 返回的是K的Set

取出所有的V值Collection<V> map.values(),返回的是K的Collection

同样的对于Map接口是有多种实现方式的

HashMap 按照散列存储,这样的存取较快,线程不安全的,允许存放null键,null值

Hashtable 线程安全,速度慢,不允许存放null键,null值

TreeMap 使用二叉树的算法来实现键值的自然排序功能。

排序接口-Collections

Collections工具类包含将集合元素赋值到另外一个集合中,集合的数据进行反转,集合中取最大值、最小值以及两个集合的交集的元素等等实用的操作

  1)sort(List<T> list):将集合List的进行按照升序进行排序

  2) binarySearch(List<? extends Comparable<? super T>> list, T key)二分查找Lisi中的元素

  3)copy(List<? super T> dest, List<? extends T> src):将src 中的数据复制到dest中

  4)max(Collection<? extends T> coll):获取集合中最大的元素

  5)min(Collection<? extends T> coll):获取集合中最小的元素

  6)reverse(List<?> list)将List进行反转

Null和empty不是一个概念,

Null 代表对象是不存在的,empty代表对象存在,只是里面没有存储值。

如果一个对象是null,我们调用其方法时会出现NullException异常

 

sort(List<T> list)对指定列表按升序进行排序,列表元素需实现Comparable接口。

sort(List<T> list, Comparator<? super T> c)列表元素无需实现Comparable接口,但是需要指定比较规则实现类。

两种啥区别的呢:

    第一种使用自身实现了Comparable接口的方法的规则排序,

    第二种按照比较器规则排序,有没有实现接口无所谓,因为有比较器。

多线程

概念

 

一个进程至少有一个线程,同一个进程中多个线程可以并发。进程间资源不能共享

例如:QQ和迅雷两进程的资源不能相互访问。线程就是进程的一个实体,可以理解为线程就是程序运行中的一条路径。同一个进程的多个线程间是可以访问资源的。而我们允许多个线程并发执行,提高了程序运行效率和资源利用率。

多线程的利弊:

       利:多线程可以提高程序运行效率和资源的利用率。

       弊:多线程会比较消耗资源,效率比较低。处理不好的话会造成线程死锁。

实现线程

在Java中实现线程有两种方式:

  1)继承Thread类

     从Thread类中实例化的对象即代表线程,启动一个线程就是建立一个Thread实例。因为完成线程真正功能的代码放在类的run()方法中,所以可以将线程要做的事写在run()方法中即可。然后调用Thread类中的start()方法执行线程,也就是调用run()方法。

  2)实现Runnable接口

     Java不支持多重继承,因此如果有一个子类要想实现线程,那就可以实现Runnable接口。实现了Runnable接口并编写run()方法,使该任务可执行你的命令。

 3)实现Callable接口

   Callable接口类似于Runnable接口,实现起来较为复杂,和Runnable接口有些不同,最主要的区别是Callable接口要实现的call()方法中可以抛出异常,且还可以在任务执行后返回值。

前两种方式是比较简单也是比较常见的,一般推荐大家实现Runnable接口来实现线程。

线程启动

任何一个线程只有启动了,才会开始工作。在前面的两个例子中,我们可以看到每个例子都由start()开启,难道我们直接调用它们的run()方法不行吗?

这个原理就相当于是玩游戏机,我们有多个人想玩,于是就start()后,就进行排队等待,等待CPU来调度,当CPU调度到我了,我就执行run()来玩游戏机。

无论用哪种方式实现多线程,都是要调用start()方法来启动线程。

调用start()方法必须是线程类,就是必须实现了Runnable()接口的类或者是继承了Thread类因为Thread也实现了Runnable接口。

Runnable接口里有一个run()方法。Java虚拟机就会自己执行这个方法。

当我们调用start()方法时,CPU会开启一条新线程,并在新线程上执行run()方法。

线程安全

我们可以采用同步方法,用synchronized修饰方法,整个方法的代码都是同步的,只能一个线程运行。同步方法使用this作为锁。

还可以采用同步代码块,同步代码块中的内容同一时间内只能执行一个线程。同步代码块形式如下:

synchronized(锁对象—临界资源){
中间是要同步的代码
}

在多个线程同时竞争资源的时候“锁”的出现就能解决线程之间因竞争资源而出现的冲突。为了防止这种冲突出现的方法就是当资源被一个任务使用时,在其上加锁。

第一个访问资源的线程必须锁定资源,使其它线程在其解锁前,就无法访问它,而且在解锁之后,另一个任务就可以锁定并使用它,以此类推。

“锁”可以帮助某个线程锁定资源,使资源仅能被当前线程使用,当线程运行完毕后,会释放锁,其他的线程才能锁定资源并使用。

lock();//加锁
需要同步的代码块
unlock();//解锁

注意:解锁unlock(),最好是放在finally中,因为如果上面的代码抛出了异常没有解锁的话,会导致一直就停留在这,无法运行别的线程,程序卡死。

在多个线程并发执行使用多个锁来同步时,有可能互相冲突,导致程序无法继续执行。

线程死锁:

  现有线程1、线程2,资源A和资源B。线程1完成需要有资源A和B,此时线程1已有资源A;线程2完成需要有资源A和B,此时线程2已有资源B;这样两个线程就都完成不了,这就叫线程死锁。

一旦程序发生死锁,程序将死掉

1)线程同步的目的是为了保护资源,防止多个线程访问同一个资源时破坏了资源。

2)线程同步方法是通过锁来实现,每个对象都有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其它访问该对象的线程就无法再访问该对象的其他同步方法。

3)对于同步,要时刻清楚在哪个对象上同步,这是关键。

4)当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。

5)使用lock()来代替synchronized时,一定要解锁并注意unlock()的位置

线程的通信

sleep();----强制正在执行的线程休眠(暂停执行),单位是毫秒(不释放资源)。

注意:线程睡眠到期自动苏醒,并返回到可运行的状态。sleep()中指定的时间是指线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。

wait();当前线程暂停,等待notify()来唤醒(释放资源)。

join();当前线程暂停,等待加入的线程运行结束,当前线程继续执行。

使用锁对象的notify()方法可以将正在等待的线程唤醒,但是同时有多个线程都处于等待状态,notify()只是随机唤醒一个。

注:唤醒后的进程进入就绪态,而不是进入运行态。

 

通信:

       Condition是一个接口,其基本方法就是await()和signal()方法,用这两个方法来实现控制指定线程的等待和唤醒的功能。

        await()方法可以控制线程等待。

        signal()方法可以唤醒等待的线程。

        signalAll()方法可以唤醒所有等待的线程。

使用Lock对象的newCondition()方法获取一个Condition对象,调用Condition对象的await()方法、signal()方法和signAll()方法都必须在lock的保护之内,也就是说在lock()(加锁)和unlock()(解锁)之间进行调用。

总结

1)进程是具有一定独立功能的程序,进程是系统进行资源分配和调度的一个独立单位;线程是进程的一个实体,是CPU调度和分配的基本单位;同一进程中多个线程间的资源是共享的

  2)JAVA中可以通过实现Runable接口和继承Thread类来实现多线程

  3)多线程Thread类中有一些方法能够获取线程名称、线程调度,优先级设置等操作

  4)多线程在共用资源的时候会产生一些安全问题,这样我们在使用多线程时就尽量避免共用资源,否则一个线程在使用该资源,另外一个线程也使用该资源会出现出乎意料的结果

  5)为了避免一个资源在多个线程中出现资源安全问题,可以在使用该资源时进行添加同步锁,这样就可以使一个线程在使用该资源时,另一线程要必须等待了,但是如果设计不合理会出现循环等待“死锁”的现象

  6)借助生产者消费者模式,掌握线程间通信、线程的状态及状态转换,wait notify和sleep,join,yeild等方法都可以在线程间进行协作来协调多个线程的配合

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值