集合Collection
简述
由来:对象用于封装数据,对象多了需要存储,集合可以存储对象;对象的数目不确定,可以用集合容器进行存储。
集合的特点:
1.用于存储对象的容器
2.长度是可变的
3.不可用来存储基本数据类型
从内部的数据结构设计的不同,集合可以有多种,现在学习3种:List,Set,Map。
P.S.
数组和集合都是容器,有什么不同?
1.数组也可以存储对象,但是它的长度是固定的;集合的长度是可变的。
2.数组可以存储基本数据类型;集合只能存对象。
Collection接口:
public interface Collectionextends Iterable,扩展自Iterable接口。
集合框架常用接口,其下有两个常用的子接口:List和Set。
常用方法:
1.添加:
boolean add(E e): 确保此 collection 包含指定的元素(可选操作)。
boolean addAll(Collection
2.删除:
boolean remove(Object o):从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。(注意返回值是boolean类型)
boolean removeAll(Collection
3.判断
boolean contains(Object o):如果此 collection 包含指定的元素,则返回 true。
boolean containsAll(Collection
4.获取,得到
int size():返回此 collection 中的元素数。
Object[] toArray():返回包含此 collection 中所有元素的数组。
5.取交集
boolean retainAll(Collection
迭代
集合采用迭代器的方式来取元素。
当不足以用一个方法来操作集合,那么就把这些个动作封装成对象。把取出方式定义在集合的内部,那么取出方式就可以直接访问集合内部的元素。那么取出方式就被定义成内部类。
每一种容器的数据结构都不同,具体的取出方式也不一样,但存在共性:取出和判断,将共性取出。
这些内部类都符合一个规则。该规则就是Iterator接口。通过一个对外提供的方法iterator(),来获取集合的取出对象。
因为Collection中有iterator方法,所以每一个子类集合对象都具备迭代器。
迭代器常用方法:
boolean hasNext(): 如果仍有元素可以迭代,则返回 true。
E next():返回迭代的下一个元素。
void remove():从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
使用:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class MyCollection1 {
public static void main(String[] args) {
Collection co = new ArrayList<>();
co.add("abc1");
co.add("abc2");
co.add("eee");
Iterator it = co.iterator();
//判断
while (it.hasNext()) {
//取
System.out.println(it.next());
}
}
}
P.S.
迭代器的next方法返回值类型是Object,所以要记得类型转换。
collection 进行迭代的迭代器。迭代器取代了 Java Collections Framework 中的 Enumeration。
迭代器与枚举有两点不同:
1.迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素。
2.方法名称得到了改进。很多方法名在枚举中显得过长,在迭代器中得到了改善。
List接口
常用的实现类:
|–ArrayList:底层的数据结构使用的是数组结构。特点:查询速度很快。但是增删稍慢。线程不同步。
|–LinkedList:底层使用的是链表数据结构。特点:增删速度很快,查询稍慢。
|–Vector:底层是数组数据结构。线程同步。被ArrayList替代了。
List接口的常用方法:
1.增加:
boolean add(E e):向列表的尾部添加指定的元素(可选操作)。
void add(int index, E element:在列表的指定位置插入指定元素(可选操作)。
boolean addAll(Collection
2.删除:
E remove(int index):移除列表中指定位置的元素(可选操作)。
boolean remove(Object o):从此列表中移除第一次出现的指定元素(如果存在)(可选操作)。
boolean removeAll(Collection
3.修改
E set(int index, E element):用指定元素替换列表中指定位置的元素(可选操作)。返回:以前在指定位置的元素。
4.查询
E get(int index):返回列表中指定位置的元素。
List subList(int fromIndex, int toIndex):返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。获取部分对象元素
5.其他
ListIterator listIterator():返回此列表元素的列表迭代器(按适当顺序)。
ListIterator listIterator(int index)返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。
我们发现,List接口中不仅有Iterator()方法,还有一个名为:listIterator()方法,此方法返回一个ListIterator接口实例。
ListIterator接口
在使用迭代器Iterator时,不可以操作集合对象的方法操作数据,否则会出:Exception in thread “main” java.util.ConcurrentModificationException异常。所以只能使用迭代器方法使用元素,但是Iterator接口只提供了3个方法,hasNext();next();remove();方法很贫乏,所以扩展出 ListIterator()接口,增加了很多方法操作元素。
该接口只能通过List集合的ListIterator方法获取。
增加的方法:
void add(E e): 将指定的元素插入列表(可选操作)。
boolean hasPrevious():如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。
E previous():返回列表中的前一个元素。
int previousIndex():返回对 previous 的后续调用所返回元素的索引。
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class MyCollection1 {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("abc1");
list.add("abc2");
list.add("eee");
ListIterator it = list.listIterator();
it.add("abc3");
it.add("abc4");
//证明add方法是添加到next之前,或者previous之后
while(it.hasPrevious()){
System.out.println(it.previous());
}
//需要重新再取一次
it = list.listIterator();
while(it.hasNext()){
System.out.println("add(正序打印):"+it.next());
}
//set()方法
it.previous();
it.set("aaa");
it.next();
while(it.hasPrevious()){
System.out.println("set(eee-->aaa)(倒叙打印):"+it.previous());
}
}
}
vector
它是线程不安全的存储:
/**
* vector线程安全问题
*/
import java.util.Vector;
public class ThreadVecctor1 {
public static void main(String[] args) {
//创建一个Vector,并添加元素
Vector v = new Vector();
for(int i=0;i<1000;i++){
v.add("abc"+i);
}
System.out.println(v);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
//开启多线程
Demo2 d1 = new Demo2(v);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d1);
t1.start();
t2.start();
}
}
class Demo2 implements Runnable{
private Vector v;
public Demo2(Vector vec){
this.v = vec;
}
public void run(){
while(true){
if(!v.isEmpty()){
for(int j=0;j<999999;j++){}
String s = (String) v.remove(v.size()-1);
System.out.println(Thread.currentThread().getName()+"::::"+s);
}
}
}
}
运行结果:
同一个Vector对象是不允许多个线程同时进行同一个操作的。所以,即使没有添加同步代码块,vector也会依照同步进行操作。
与之相比较的是ArrayList类,它的线程就是非同步的。
/**
* ArrayList的线程安全问题
*/
import java.util.ArrayList;
public class ThreadArrayList {
public static void main(String[] args) {
ArrayList al = new ArrayList();
for (int i = 0; i < 1000; i++) {
al.add("abc" + i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
Demo1 d1 = new Demo1(al);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d1);
t1.start();
t2.start();
}
}
class Demo1 implements Runnable {
private ArrayList a;
public Demo1(ArrayList al) {
this.a = al;
}
public void run() {
while (true) {
if (!a.isEmpty()) {
for (int j = 0; j < 999999; j++) {
}
String s = (String) a.remove(a.size() - 1);
System.out.println(Thread.currentThread().getName() + "::::" + s);
}
}
}
}
可以看到,字符串”abc997”被不同线程同时操作了,说明了ArrayList的线程不同步。
另外,Vector的常用方法与List接口基本相当。他的取出方式有两个,一个是常用的Iterator,另一种是已经不常用被Iterator取代了的Enumeration枚举。
import java.util.Enumeration;
import java.util.Vector;
public class MyVector1 {
public static void main(String[] args) {
Vector v = new Vector();
v.add("v1");
v.add("v2");
v.add("v3");
Enumeration en = v.elements();
// hasMoreElements(),等价Iterator类的hasNext()
while (en.hasMoreElements()) {
// nextElement(),等价next()
System.out.println(en.nextElement());
}
}
}
ArrayList类
P.S.
注意ArrayList是线程不同步的,如果需要多线程操作,需要进行手动加锁来完成同步操作。
/**
* ArrayList对对象的存取
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MyArrayList1 {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Person("list1", 21));
list.add(new Person("list2", 22));
list.add(new Person("list3", 23));
list.add(new Person("list4", 24));
Iterator it1 = list.iterator();
while (it1.hasNext()) {
Person p = (Person) it1.next();
System.out.println(p.getName() + "::::" + p.getAge());
}
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
定义一个方法可以去除ArrayList类中的相同的元素。
/**
* 添加一个方法,可以去除ArrayList中的重复元素
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Demo3 {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
list.add("abc1");
list.add("abc1");
list.add("acb2");
list.add("acb3");
list.add("acb4");
System.out.println(list);
ArrayList al = arrayListSet(list);
System.out.println(al);
}
public static ArrayList arrayListSet(ArrayList list) {
ArrayList temp = new ArrayList<>();
Iterator it = list.iterator();
while(it.hasNext()){
Object o = it.next();
if(!temp.contains(o)){
temp.add(o);
}
}
return temp;
}
}
LinkedList
LinkedList是以链表形式存取数据的。即他的逻辑结构是连续的,每一个元素数据都存有前一个和后一个元素的索引值,方便查找。
所以,LinkedList的特点是查找速度快。
特有方法
1.添加
void addFirst(E e):将指定元素插入此列表的开头
void addLast(E e):将指定元素添加到此列表的结尾。
1.6版本以后:
boolean offerFirst(E e ):在此列表的开头插入指定的元素。
boolean offerLast(E e):在此列表末尾插入指定的元素。
2.获取:
E getFirst(): 返回此列表的第一个元素。
E getLast():返回此列表的最后一个元素。
1.6版本以后:
E peekFirst():获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。
E peekLast():获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
E pollFirst():获取并移除此列表的第一个元素;如果此列表为空,则返回 null。
E pollLast():获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。
3.删除:
E removeFirst():移除并返回此列表的第一个元素。
boolean removeFirstOccurrence(Object o):从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
E removeLast():移除并返回此列表的最后一个元素。
boolean removeLastOccurrence(Object o):从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。
队列:先进先出,First In First Out FIFO
/**
* 写一个自己的队列类
*/
import java.util.LinkedList;
public class MyQueueTest{
public static void main(String[] args) {
Queue q1 = new Queue();
q1.queueAdd("abc1");
q1.queueAdd("abc2");
q1.queueAdd("abc3");
q1.queueAdd("abc4");
while(!q1.isNull()){
System.out.println(q1.queueGet());
}
}
}
class Queue{
private LinkedList list = new LinkedList<>();
public void queueAdd(Object o){
list.addLast(o);
}
public boolean isNull(){
return list.isEmpty();
}
public Object queueGet(){
return list.removeFirst();
}
}
Set接口
实现该接口的类必须保证元素的唯一性,且元素是无序的(存入和取出顺序不一定一致)
该接口下的常用子类:
|–HashSet:底层数据结构是哈希表。线程不同步。保证线程唯一性的原理:判断元素的hashCode值是否相等,如果相等,还会判断元素的equals方法。
|–TreeSet:使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。 底层数据结构是二叉树。
|–LinkedHashSet:按照插入顺序排列
HashSet类
线程不安全,存取速度快。
通过hashCode和equals方法保证元素的唯一性,具体步骤,先判断元素的hashCode是否先等,如相等,在判断equals方法。如果hashCode值不等,则不需要判断equals。
对于对象的存取,需要重写hashCode和equals方法,否则,使用默认的这两个方法,你得到的对象永远是不同的,也就是说,自定义的对象不重写这两个方法,会在set集合中存在相同的对象。
例:
import java.util.HashSet;
import java.util.Iterator;
public class Demo2 {
public static void main(String[] args) {
HashSet hs1 = new HashSet();
hs1.add(new Person("hashset1", 21));
hs1.add(new Person("hashset1", 21));
hs1.add(new Person("hashset1", 21));
hs1.add(new Person("hashset1", 21));
Iterator it1 = hs1.iterator();
while (it1.hasNext()) {
Person p1 = (Person) it1.next();
System.out.println(p1.getName() + ":::" + p1.getAge());
}
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int hashCode() {
// *37保证不同的数据不会发生hash值碰撞
return this.age * 37 + this.name.hashCode();
}
public boolean equals(Object obj) {
if (obj.getClass() != this.getClass()) {
throw new ClassCastException("类型错误!");
}
Person p = (Person) obj;
// 比年龄
int temp = p.age - this.age;
if (temp != 0) {
return false;
}
// 比名字
if (!(p.name.equals(this.name))) {
return false;
}
return true;
}
}
当不重写hashcode和equals方法时:
我们认为相同的元素,在set集合认为是不相同的。
所以需要重写这两个方法:
这样就得到了不存在相同对象的集合了。
TreeSet类
使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
构造方法:
可以看到,TreeSet要求存入的对象要实现Comparable接口,或者提供比较器,否则会报ClassCastException异常,表明元素不是 Comparable,或者是不可相互比较的。
import java.util.TreeSet;
public class TreeeSet1 {
public static void main(String[] args) {
//使用元素的自然顺序对元素进行排序
TreeSet tr = new TreeSet<>();
tr.add("zzz");
tr.add("aaa");
tr.add("ccc");
System.out.println(tr);
}
}
TreeSet判断元素的唯一性,通过compareTo方法返回值进行判断,返回0则表明添加元素相同,并通过返回值的正负来排序。
实现TreeSet中对象的有序排列:
方法一:让元素自身具备比较功能,实现Comparable接口,覆盖compareTo方法。
实现:
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSet2 {
public static void main(String[] args) {
TreeSet tr = new TreeSet<>();
tr.add(new Student("abc1",20));
tr.add(new Student("abc1",20));
tr.add(new Student("abc1",21));
tr.add(new Student("abc2",22));
tr.add(new Student("abc3",20));
Iterator it = tr.iterator();
while(it.hasNext()){
Student s = (Student) it.next();
System.out.println(s.getName()+"::::"+s.getAge());
}
}
}
class Student implements Comparable {
private String name;
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//覆盖compareTo
public int compareTo(Object o) {
if (o.getClass() != this.getClass()) {
throw new ClassCastException("转型错误!");
}
if (this == o) {
return 0;
}
Student s = (Student) o;
int temp = this.age - s.age;
if (temp == 0) {
return this.name.compareTo(s.name);
}
return temp;
}
}
方法二:定义一个类实现Comparator接口,覆盖compare方法
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSet3 {
public static void main(String[] args) {
TreeSet ts = new TreeSet<>(new MyComparator());
ts.add(new Employee("abc1", 21));
ts.add(new Employee("abc1", 21));
ts.add(new Employee("abc2", 21));
ts.add(new Employee("abc1", 22));
ts.add(new Employee("abc4", 21));
Iterator it = ts.iterator();
while(it.hasNext()){
Employee e = (Employee) it.next();
System.out.println(e.getName()+":"+e.getAge());
}
}
}
class Employee{
private String name;
private int age;
public Employee(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
class MyComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
Employee e1 = (Employee) o1;
Employee e2 = (Employee) o2;
int temp = e1.getAge() - e2.getAge();
if(temp==0){
return e1.getName().compareTo(e2.getName());
}
return temp;
}
}
当Comparable和Comparator都存在时,以Comparator比较器为主。
Map接口
特点:键值对<key-value>
,key用来作为标识键,value则用来表示key所对应的键值。
常见实现类
Map
|--Hashtable:底层是哈希表数据结构,不可以存入null键null值。该集合是线程同步的。JDK1.0,效率低。
|--HashMap:底层是哈希表数据结构。允许使用null键null值,但是null键不可以有多个,null值可以有多个。该集合是不同步的。JDK1.2,效率高。
|--TreeMap:底层是二叉树数据结构。线程不同步。可以用于给Map集合中的键进行排序。
P.S
|--AbstractMap类:HashMap的父类
Map集合的取出方式
第一种(常用的)
通过map.entrySet()来取数据,取得的数据是Map.Entry类型的
public class Demo1 {
public static void main(String[] args) {
Map map = new HashMap();
map.put(1, "abc1");
map.put(2, "abc2");
map.put(3, "abc3");
map.put(4, "abc4");
Set<Map.Entry> set = map.entrySet();
Iterator<Map.Entry> it = set.iterator();
while(it.hasNext()){
Map.Entry me = it.next();
System.out.println(me.getKey()+":::"+me.getValue());
}
}
}
第二种
通过keyMap()方法得到一个key的set集合,然后通过get(Object key),在得到对应的键值。
public class Demo1 {
public static void main(String[] args) {
Map map = new HashMap();
map.put(1, "abc1");
map.put(2, "abc2");
map.put(3, "abc3");
map.put(4, "abc4");
Set s = map.keySet();
Iterator it = s.iterator();
while(it.hasNext()){
System.out.println(it.next()+":::"+map.get(it.next()));
}
}
}
Map.Entry
/*
获取该字符串中的字母出现的次数,如:"sjokafjoilnvoaxllvkasjdfns";
希望打印的结果是:a(3)c(0).....
思路:1.字符是唯一的,目的是想统计字符的数目
2.使用Map集合,将字符作为key值,统计数目作为value
3.Map集合添加键值对,判断键值是否存在,不存在key值,则,默认加入<字符-1>键值对;
存在,则取出该字符对应数目,在此基础上在加1,然后更新键值表
4.取出Map元素,使用StringBuffer来返回一个字符串。
*/
public class Test1 {
public static void main(String[] args) {
String str = "sjokafjoilnvoaxllvkasjdfns";
String s = getCharNum(str);
System.out.println(s);
}
public static String getCharNum(String str) {
String s = null;
Map<Character, Integer> map = new HashMap<Character, Integer>();
char[] c = str.toCharArray();
for (int i = 0; i < c.length; i++) {
if ((c[i] >= 'a' && c[i] <= 'z') || (c[i] >= 'A' && c[i] <= 'Z')) {
int num = 1;
if (map.containsKey(c[i])) {
num = map.get(c[i]);
num++;
}
map.put(c[i], num);
}
}
StringBuffer stb = new StringBuffer();
Iterator<Map.Entry<Character, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Character, Integer> me = it.next();
stb.append(me.getKey() + "(" + me.getValue() + ")");
}
s = stb.toString();
return s;
}
}