集合Collection (List And Set)
1.在实际开发中,需要将使用的对象存储于特定的数据结构容器中。JDK提供了这样的容器-集合(Collection)。
2.Collection 是一个接口,其子接口有:List和Set
3.List和Set的区别:List是有序可重复集(可以存储重复元素),Set是无序不可重复集(不能存储重复元素)。元素是否重复取决于元素的equals()比较结果。
集合持有对象的引用
集合中存储的都是引用类型元素,并且集合只保存每个元素的引用,而非将元素本身存入集合。基本类型数据会自动装箱为Integer类型数据。
Collection 中的常用方法
add()方法
1.Collection 定义了一个add方法用于向集合中添加新元素
-boolean add(E e) E表示的是数据类型
该方法会将给定的元素加进集合 若添加成功则返回true,否则返回false
演示代码如下:
public void testAdd() {
Collection<String> c = new ArrayList<String>();
System.out.println(c);//[]
c.add("a");
c.add("b");
c.add("c");
System.out.println(c);//[a,b,c]
}
contains()方法
-boolean contains(Object o)
该方法用于判断给定的元素是否包含在集合中,若包含则返回true,否则返回false。
注意:集合判断元素是否被包含在集合中是根据equals方法,进行比较后的结果。
通常类有必要重写equals方法 来保证比较的合理性。
演示代码如下:
public void testContains() {
Collection<Person> persons = new ArrayList<Person>();
persons.add(new Person("张三",20));
persons.add(new Person("李四",21));
persons.add(new Person("王五",22));
persons.add(new Person("赵六",23));
Person p =new Person("李四",21);
//List集合contains方法和对象的equals方法有关
boolean flag = persons.contains(p);
//如果Person类不重写equals方法将为false
System.out.println(flag);//true
}
int size(),void clear,boolean isEmpty
-int size()返回当前集合中元素的总数
-void clear() 该方法清空当前集合
-boolean isEmpty()该方法用于判断集合是否为空
演示代码如下:
public void testSizeAndClearAndIsEmpty() {
Collection<String> c = new HashSet<String>();
System.out.println(c.isEmpty());//true
c.add("Java");
c.add("C");
c.add("php");
c.add("C#");
c.add("Java");
System.out.println(c);//无序不可重复:[C#, Java, C, php]
System.out.println("isEmpty:"+c.isEmpty()+",size:"+c.size());//false 4
c.clear();
System.out.println("isEmpty:"+c.isEmpty()+",size:"+c.size());//true 0
}
addAll()方法和containsAll()方法
-boolean addAll(Collection<? extends E> c)
该方法需要我们传入一个集合,并将该集合中的所有元素添加到当前集合中
如果此collection 由于调用而发生更改,则返回true
-boolean containsAll(Collection<?> c)
该方法用于判断当前集合是否包含给定集合中的所有元素
若包含则返回true
演示代码如下:
public void testAddAllAndContainsAll() {
Collection <String >c1 = new ArrayList<String>();
c1.add("java");
c1.add("c");
c1.add("php");
c1.add("c#");
c1.add("js");
System.out.println(c1);//java c php c# js
Collection <String> c2 = new HashSet<String>();
c2.addAll(c1);
System.out.println(c2);//无序 java php c js c#
Collection <String>c3= new ArrayList<String>();
c3.add("java");
c3.add("c");
System.out.println(c1.containsAll(c3));//true
}
}
迭代器 Iterator
迭代器用于遍历集合元素,获取迭代器可以使用Collection定义的方法:
Iterator <E> it = 集合名.iterator();
迭代器是一个接口,集合在重写了Collection的iterator()方法时利用内部类提供了迭代器的实现。
Iterator提供了统一的遍历集合的方式,其提供了用于遍历集合的两种方法:
boolean hasNext():判断集合是否还有元素可以遍历
E next():返回迭代的下一个元素
演示代码如下:
public void testHashNextAndNext() {
Collection<String> c = new HashSet<String> ();
c.add("java");
c.add("c");
c.add("c#");
c.add("php");
c.add("python");
//获取迭代器
Iterator<String> it = c.iterator();
//判断是否有元素 然后遍历
while(it.hasNext()) {
//返回元素
String s = it.next();
System.out.println(s);
}
}
remove()方法
在使用迭代器遍历集合时,不能通过集合的remove()删除集合中的元素,否则会抛出并发更改异常。
void remove()
1.我们通常使用迭代器自身提供的remove()方法来删除通过next()方法迭代出来的元素。
2.迭代器的删除方法是在原集合中删除元素。
3.这里需要注意的是:在调用remove方法前必须通过迭代器的next方法迭代过的元素,
那么删除的就是这个元素,并且不能再次调用remove方法,除非再次调用next方法后
方可调用remove()方法
演示代码如下:
public void testRemove() {
Collection<String> c = new HashSet<String>();
c.add("java");
c.add("C");
c.add("C#");
c.add("PHP");
c.add("python");
System.out.println(c);
Iterator<String> it = c.iterator();
while(it.hasNext()) {
String s = it.next();
if(s.indexOf("C")!= -1) { //不等于-1 是找到 等于-1没有找到
it.remove();//删除包含字母C的元素
}
}
System.out.println(c);//python java php 把包含C字母的元素全部删除
}
增强for循环
java5.0版本之后推出了一个新特性,增强for循环,也称为新循环,该循环不同于传统的循环工作,只用于遍历数组和集合。
语法:
for (元素类型 e:集合或者数组){
循环体;
}
新循环并非新语法,而是在编译过程中,编译器会将新循环转化为迭代器模式
所以可以说新循环的本质是迭代器
演示代码如下:
public void testFor() {
Collection<String> c = new HashSet<String>();
c.add("java");
c.add("c");
c.add("php");
c.add("c#");
c.add("python");
for (String string : c) {
System.out.println(string.toUpperCase()+" ");
//C# PYTHON JAVA C PHP
}
}
泛型机制
1.泛型机制是JavaSE 5.0引入的特性,泛型的本质是参数化类型,在类 接口 和方法的定义过程中,所操作的数据类型被传入的参数指定。
2.Java泛型机制广泛的应用在集合框架中,所有的集合类型都带有泛型参数,这样在创建集合时可以指定放入集合中元素的类型,java编译器可以据此类型进行检查,这样就可以减少代码在运行时出现的错误可能性。
3.ArrayList类的定义中,中的E为泛型参数,在创建对象时可以将类型作为参数传递,此时类定义所有的E将被替换成传入的参数。
演示代码如下:
public void testE() {
ArrayList<String> list = new ArrayList<String>();
list.add("One");//只能添加String类型的数据
}
集合操作-线性表
List
1.List接口是Collection的子接口,用于定义线性表数据结构,可以将List理解为存放对象的数组,只不过元素个数可以动态的增加或者减少。
2.List接口的两个常见实现类为ArrayList和LinkedList分别用动态数组和链表的方式实现了List接口
3.可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别。ArrayList更适合于随机访问,LinkedList更适合插入和删除。
常用方法
List除了继承Collection定义的方法外,还根据其线性表的数据结构定义了一系列的方法,其中最常用的就是基于下标的get和set方法。
E get(int index) 获取集合中指定下标对应的元素,下标从0开始
E set(int index,E element)将指定的元素存入指定的位置,并将原位置元素返回
演示代码如下:
public void testGetAndSet() {
List<String> list = new ArrayList <String>();
list.add("java");
list.add("c++");
list.add("php");
list.add("python");
list.add("c#");
//get方法遍历List
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i).toUpperCase());
//JAVA C++ PHP PYTHON C#
}
String value = list.set(1, "hadoop");
System.out.println(value);//c++
System.out.println(list);//java hadoop php python c#
//先拿到hadoop 将 hadoop插入索引3的位置 把python 返回,然后再把python插入索引1的位置
list.set(1, list.set(3, list.get(1)));
System.out.println(list);
//java python php hadoop c#
}
根据下标的操作进行插入和删除
void add(int index,E element)将给定的元素插入到指定位置,原位置以及后续
元素都顺序向后移动
E remove(int index):删除给定位置的元素,并将被删除的元素返回
演示代码如下:
public void testInsertAndRemove() {
List<String> list = new ArrayList<String>();
list.add("java");
list.add("c++");
System.out.println(list);//java c++
list.add(1,"cpp");
System.out.println(list);//java cpp c++
String a = list.remove(2);
System.out.println(list+".."+a);//java cpp...c++
}
获取子List(sublist)
注意sublist获取的List与原List占用相同存储空间,对子List的操作会影响原List
List<E>sublist(int fromIndex,int toIndex) fromIndex和toIndex是截取子
List的首尾下标,前包后不包。
演示代码如下:
public void testSubList() {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
System.out.println(list);//0-9
List<Integer> sublist = list.subList(3, 8);//3 4 5 6 7
//subList获得的List和源List占有相同的资源
for (int i = 0; i < sublist.size(); i++) {
sublist.set(i, sublist.get(i)*10);
}
System.out.println(sublist);//30 40 50 60 70
//用的是同一块 存储空间 对子List操作会影响原List
System.out.println(list);// 0 1 2 30 40 50 60 70 8 9
//注意clear只能删除连续元素
list.subList(3, 8).clear();
System.out.println(list);// 0 1 2 8 9
}
List与数组之间的转化
1.List的toArray方法用于将集合转化为数组,但实际上该方法是在collection的接口中定义的,所以所有的集合都具备这个功能。
Object[] toArray()
<T> T[] toArray(T[] a)
其中第二个方法是比较常用的,我们可以传入一个指定的类型的数组,该数组元素类型
一致。返回值是转换后的数组,该数组会保留集合中的所有元素
演示代码如下:
public void testToArray() {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
String [] strArr = list.toArray(new String[] {});
System.out.println(Arrays.toString(strArr));//[a,b,c]
}
2.Arrays数组类中提供了一个静态方法asList 使用该方法我们可以将一个数组转化为对应的List集合
static <T> List<T> asList<T...a>
返回的List集合元素类型由传入的数组的元素类型决定
返回的集合我们不能对其增删元素,否则会抛出异常,并且对集合的元素进行修改
会影响数组对应的元素。
演示代码如下:
public void testArrayToList() {
String [] strArr = {"a","b","c"};
List<String> list = Arrays.asList(strArr);
System.out.println(list);
//list.add("d");//抛出异常
//System.out.println(list);
List<String> list1 = new ArrayList<String>();
list1.addAll(Arrays.asList(strArr));
list1.add("d");
System.out.println(list1);
/*
* 数组转化为List是不能增删的 如果需要增删 可以利用空集合调用addAll方法将转换后的集合全部添加 新集合
* 就可以进行操作了。
*/
}
List排序
1.Collections是集合的工具类,它提供了很多便于我们操作集合的方法,其中就有用于集合排序的sort方法
该方法定义为:
-void sort(List<T> list)
该方法的作用是对给定的集合元素进行自然排序
演示代码如下:
public void testSort() {
List<Integer> list = new ArrayList<Integer>();
Random r = new Random();
for (int i = 0; i <10;i++) {
list.add(r.nextInt(100));
}
System.out.println(list);//[7, 77, 94, 15, 6, 83, 35, 30, 24, 49]
Collections.sort(list);//从小到大排序
System.out.println(list);//[6, 7, 15, 24, 30, 35, 49, 77, 83, 94]
}
Comparable
Collections的sort方法是对集合的自然排序,那么两个元素对象之间就一定要分大小,这个大小之分是如何判断的呢?实际上在使用Collections的sort排序的集合元素都必须是Comparable接口的实现类。
该接口表示子类是可以比较的,因为实现接口必须重写抽象方法
int compareTo(T t);
该方法用于使当前对象与给定对象进行比较
若当前对象大于给定对象 返回值为大于0的整数
若当前对象小于给定对象 返回值为小于0的整数
若两个对象相等 则返回0
演示代码如下:
public void testComparable() {
/*
* Cell实现了Comparable 接口
* compareTo 方法逻辑为 按照x值大小进行排序
*/
List<Cell> cells = new ArrayList<Cell>();
cells.add(new Cell(2,3));
cells.add(new Cell(5,1));
cells.add(new Cell(3,2));
Collections.sort(cells);
System.out.println(cells);
2,3 3,2 5,1
}
Comparator
1.一旦Java类实现了Comparable接口,其比较逻辑就已经确定,如果希望在排序的操作中,临时指定比较规则,可以采用Comparator接口回调的方式
2.Comparator 接口要求实现类必须重写其定义的方法:
int compare(T o1,T o2)
若o1>o2 方法的返回值应大于0
若o1<o2 方法的返回值应小于0
若o1=o2则返回0
演示代码如下:
public void testComparator() {
List<Cell> cells = new ArrayList<Cell>();
cells.add(new Cell(2,3));
cells.add(new Cell(5,1));
cells.add(new Cell(3,2));
//临时按照y排序 this.y-o.y
Collections.sort(cells, new Comparator<Cell>() {
@Override
public int compare(Cell o1, Cell o2) {
// TODO Auto-generated method stub
return o1.y-o2.y;
}
});
System.out.println(cells);//5,1 3,2 2,3
//临时回调
Collections.sort(cells);
System.out.println(cells);//2,3 3,2 5,1
}
比较总结
1.对于集合比较实用Collection.sort()
2.对于集合中的对象比较,需要指定比较逻辑,需要实现Comparable接口并重写compareTo方法自定义逻辑
3.对于需要临时改变比较规则,需要使用Collections.sort(List,Comparator)采用回调方式重写Comparator接口的compare方法自定义逻辑。
Queue和Deque
Queue
1.队列Queue是常用的数据结构,可以将队列看成特殊的线性表,队列限制了对线性表的访问方式,只能从线性表的一端添加元素(offer)从另一端取出(poll)元素
2.队列遵循先进先出的原则
3.JDK提供了Queue接口,同时使得LinkedList实现了该接口,选择LinkedList实现Queue接口的原因在于Queue经常要进行添加和删除的操作,而LinkedList在这方面效率比较高。
4.Queue接口主要方法如下:
boolean offer(E e):将一个对象添加至队尾,如果添加成功则返回true
E poll():从队首删除并返回一个元素
E peek():返回队首元素但是不删除
演示如下代码:
public void testQueue() {
Queue<String> queue = new LinkedList<String>();
queue.add("a");
queue.offer("b");
queue.offer("c");
System.out.println(queue);//a b c
String str = queue.peek();
System.out.println(str);//a
String str1 = queue.poll();
System.out.println(str1);//a
System.out.println(queue);//b c
while(!queue.isEmpty()) {
str = queue.poll();
//遍历只能用删除队首 poll方法 删除后队首元素会变为第二个元素 而peek不会改变队首元素
System.out.println(str+" ");//b c
}
}
Deque
1.Deque是Queue的子接口,定义了所谓的 双向队列。即从队列的两端分别可以入队(offer)和出队(poll)。LinkedList实现了该接口
2.如果将Deque限制为只能从一端入队和出队,则可实现 栈 Stack 的数据接口
入栈称为push 出栈称为pop
3.栈遵循先进后出的原则
public void testStack() {
Deque<String> stack = new LinkedList<String>();
stack.push("a");
stack.push("b");
stack.push("c");
System.out.println(stack);//c b a
String s = stack.peek();
System.out.println(s);//c
while(!stack.isEmpty()) {
s = stack.pop();
System.out.println(s+" ");// c b a
}
System.out.println(stack);//[]
}
}
Map接口
1.Map接口定义的集合又称之为查找表,用于存储所谓的"Key-Value"映射对。Key可以看成是Value的索引,作为key的对象在集合中不可以重复。
2.根据内部数据结构不同,Map接口有多种实现类,其中常用的有内部为hash表实现的HashMap和内部为排序二叉树实现的TreeMap
put()方法
1.Map接口中定义了向Map中存放元素的put方法
V put(K key,V value)
2.将key-value对存入Map,如果在集合中已经包含了该Key,则操作将替换该key所对应的value,返回值为该key原来所对应的value,如果没有包含该Key返回null。
演示代码如下:
//创建成员变量
Map<String,Person> persons = new HashMap<String,Person>();
@Before //表示在每执行一次test就执行一次before
public void testPut() {
persons.put("张三", new Person("张三",80));
persons.put("闫伟", new Person("闫伟",81));
//System.out.println(persons);
}
3.Map接口中定义了从Map中获取元素的get方法
V get(Object key)返回参数key所对应的value对象,如果不存在则返回null
演示代码如下:
@Test
public void testGet() {
Person p = persons.get("张三");
System.out.println(p);//Person [name=张三, age=80]
}
containsKey()方法
Map接口中定义了判断某个key是否在Map中存在
boolean containsKey(Object key)
若Map中包含给定的key则返回true
演示代码如下:
@Test
public void testContainsKey() {
System.out.println(persons.containsKey("闫伟"));//true
}
hashCode方法
1.从hashMap的原理中我们可以看到,key的hashCode()方法的返回值对HashMap存储元素时会起着
非常重要的作用,而hashCode()方法实际上是在Object中定义的
2.对于重写了equals方法的对象,一般要妥善的重写继承自Object类的hashCode方法
(Object提供的hashCode方法 将返回该对象所在内存地址的整数形式)
3.重写hashCode方法应注意两点:
-与equals方法的一致性,即equals比较返回true的两个对象其hashCode方法返回值应该相同
-hashCode返回的数值应符合hash算法的要求,如果有很多对象的hashCode返回值都相同 则会大大降低
hash表的效率,一般情况下使用IDE eclipse提供的工具自动生成hashCode方法
装载因子及hashMap优化
1.Capacity:容量,hash表里bucket的数量 也就是散列数组大小
2.Inintal capacity:初始容量,创建hash表时,初始bucket的数量,默认构建容量是16
也可以使用特定的容量
3.Size:大小,当前散列表中存储数据的数量
4.Load factor:加载因子 默认值为0.75,当向散列表增加数据时 如果size/capacity的值大于factor则扩容
并且重新散列(rehash)
5.性能优化:加载因子较小时 散列查找性能会提高,同时也浪费了散列桶的容量。0.75是性能和空间相对平衡结果
在创建散列表时 指定合理的容量,减少rehash提高性能
Map遍历
1.Map提供了三种遍历方式
遍历所有的key
遍历所有的key-value对
遍历所有的value---不常用
2.遍历所有key的方法
Set<K> keySet()
该方法返回当前Map中所有Key存入一个Set集合
演示代码如下:
@Test
public void testKeySet() {
Set<String> keyset = persons.keySet();
System.out.println(keyset);//[张三, 闫伟]
for (String string : keyset) {
System.out.println("key:"+string);
/*
* key:张三
* key:闫伟
*/
}
}
entrySet()方法 遍历所有键值对
Set<Entry<k,v>> entrySet()
该方法将当前Map中的每一组key-value对封装为一个Entry对象并存入一个
Set集合返回
演示代码如下:
public void testEntrySet() {
Set<Entry<String,Person>> entrySet = persons.entrySet();
//[张三=Person [name=张三, age=80], 闫伟=Person [name=闫伟, age=81]]
System.out.println(entrySet);
for (Entry<String, Person> entry : entrySet) {
System.out.println(entry.getKey()+":"+entry.getValue());
/*
* 张三:Person [name=张三, age=80]
* 闫伟:Person [name=闫伟, age=81]
*/
}
}
有序的Map
1.使用Map接口的哈希表和链表实现,具有可预知迭代顺序,此实现与HashMap的不同之处在于:
LinkedHashMap维护着一个双向循环链表,此链表定义了迭代顺序,该迭代顺序通常就是存放元素的顺序。
2.需要注意,如果在Map中重新存入已有Key,那么key位置不会发生改变,改变的是value值。
演示代码如下:
@Test
public void testLinkedHashMap() {
LinkedHashMap<String,Person> map = new LinkedHashMap<String,Person>();
map.put("李志豪", new Person("李志豪",99));
map.put("李四", new Person("李四",18));
System.out.println(map);
//{李志豪=Person [name=李志豪, age=99], 李四=Person [name=李四, age=18]}
map.put("李四", new Person("李四",66));
System.out.println(map);
//{李志豪=Person [name=李志豪, age=99], 李四=Person [name=李四, age=66]}
}