容器
数组就是一种容器,可以在其中放置对象或基本类型数据。
数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从效率和类型检查的角度讲,数组是最好的。
数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。
比如:我们在一个用户管理系统中,要把今天注册的所有用户取出来,那么这样的用户有多少个?我们在写程序时是无法确定的。因此,在这里就不能使用数组。
1、泛型(Generics)
jdk1.5之后增加的
//泛型类声明,一个自定义容器
class MyCollection<E> {// E:表示泛型;
Object[] objs = new Object[5];
public E get(int index) {// E:表示泛型;
return (E) objs[index];
}
public void set(E e, int index) {// E:表示泛型;
objs[index] = e;
}
}
//泛型使用
public class TestGenerics {
public static void main(String[] args) {
// 这里的”String”就是实际传入的数据类型;
//jdk1.7之后第二个String可以不写
MyCollection<String> mc = new MyCollection<String>();
mc.set("aaa", 0);
mc.set("bbb", 1);
String str = mc.get(1); //加了泛型,直接返回String类型,不用强制转换;
System.out.println(str);
}
}
-
容器中泛型的使用
容器相关类都定义了泛型,我们在开发和工作中,在使用容器类时都要使用泛型。这样,在容器的存储数据、读取数据时都避免了大量的类型判断,非常便捷。
定义泛型的接口:- Collection
- List
List<String> list = new ArrayList<String>();
- Set
Set<Man> mans = new HashSet<Man>();
- Map
Map<Integer, Man> maps = new HashMap<Integer, Man>();
- Iterator
Iterator<Man> iterator = mans.iterator();
2、Collection接口
Collection 表示一组对象,它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。
Collection接口的方法
package com.zry.test01;
import java.util.ArrayList;
import java.util.Collection;
/**
* 测试Collection接口中的方法
* @author ZRY
*
*/
public class TestList {
public static void main(String[] args) {
Collection<String> c=new ArrayList<>();//建立一个String类型的容器
System.out.println(c.size());//容器尺寸
System.out.println(c.isEmpty());//判断容器是否为空
c.add("你好");//向容器中添加元素
c.add("你是");
System.out.println(c.size());
System.out.println(c);
System.out.println(c.contains("123"));//判断容器中是否有该元素
Object[] objs=c.toArray();//转化为object数组
System.out.println(objs);
c.remove("你是");//移出容器中某个元素
System.out.println(c);
c.clear();//清空容器
System.out.println(c);
}
}
/**
* 自定义实现一个ArrayList,体会底层原理
* @author ZRY
* @version 1.0
*/
public class ArrayList02<E> {
private Object[] elementData;
private int size;
private static final int DEFALT_CAPACITY=10;
//空参构造器
public ArrayList02(){
elementData=new Object[DEFALT_CAPACITY];
}
//构造器
public ArrayList02(int copacity){
elementData=new Object[copacity];
}
//添加元素方法
public void add(E element){
if(size==elementData.length){
Object[] newElement=new Object[elementData.length+(elementData.length>>1)];
System.arraycopy(elementData,0,newElement,0,elementData.length);
elementData=newElement;
}
elementData[size++]=element;
}
//删除元素
public void remove(E element){
for (int i=0;i<size;i++){
if (element.equals(get(i))){//容器中所有比较用equals
remove(i);//移出该位置的元素
}
}
}
public void remove(int index){
int numMove=elementData.length-index-1;
if (numMove>0){
System.arraycopy(elementData,index+1,elementData,index,numMove);
}
elementData[size--]=null;
}
@Override
//打印输出方法
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append("[");
for (int i=0;i<size;i++){
sb.append(elementData[i]).append(",");
}
sb.setCharAt(sb.length()-1, ']');
return sb.toString();
}
//get方法
public E get(int index){
return (E)elementData[index];
}
//set方法
public void set(E element,int index){
elementData[index]=element;
}
//索引越界异常提示
public void checkRange(int index){
//判断索引是否合法
if (index>size-1 || index<0){
//不合法控制台输出提示信息
throw new RuntimeException("索引不合法"+index);
}
}
public static void main(String[] args){
ArrayList02 a1=new ArrayList02(20);
for (int i=0;i<40;i++){
a1.add("lll="+i);
}
System.out.println(a1.toString());
a1.remove("lll=5");
System.out.println(a1.toString());
}
}
3、数组链表
- LinkedList底层用双向链表实现的存储.
- 链表的特点:查询效率低、增删效率高、线程不安全
/**
* @author ZRY
* @version 1.0
*/
public class LinkedList<E> {//一个自定义的链表类
private Node first;
private Node last;
private int size;
//向链表里面添加元素
public void add(E element){
Node node=new Node(element);
if (first==null){
node.previous=null;
node.next=null;
first=node;
last=node;
size++;
}else {
node.previous=last;
node.next=null;
last.next=node;
last=node;
size++;
}
}
//插入一个元素
public void add(E element,int index){
checkRange(index);
Node newNode=new Node(element);
Node temp=getNode(index);
if (temp!=null){
Node up=temp.previous;
up.next=newNode;
newNode.previous=up;
newNode.next=temp;
temp.previous=newNode;
}
size++;
}
//删除一个元素根据索引
public void remove(int index){
checkRange(index);
Node temp= getNode(index);
if (temp!=null){
Node up=temp.previous;
Node down=temp.next;
if (up!=null)
up.next=down;
if (down!=null)
down.previous=up;
if(index==0){
first=down;
}
if (index==size-1){
last=down;
}
size--;
}
}
//索引
private void checkRange(int index){
if (index<0 || index>size-1){
throw new RuntimeException("索引数字不合法");
}
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append("[");
//遍历链表元素
Node temp=first;
while (temp!=null){
sb.append(temp.element+",");
temp=temp.next;
}
sb.setCharAt(sb.length()-1, ']');
return sb.toString();
}
public E get(int index){
checkRange(index);
Node temp=getNode(index);
return temp!=null?(E)temp.element:null;
}
private Node getNode(int index){
Node temp=null;
if (index<=index<<1){
temp=first;
for (int i=0;i<index;i++){
temp=temp.next;
}
}else {
temp= last;
for (int i=0;i<index;i++){
temp=temp.previous;
}
}
return temp;
}
//测试
public static void main(String[] args) {
LinkedList<String> list=new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list);
list.remove(1);
System.out.println(list);
list.add("老号",1);
System.out.println(list);
}
}
class Node{
Node previous;//上一个节点
Node next;//下一个节点
Object element;//当前节点
public Node(Object element){
this.element=element;
}
public Node(Node previous,Node next,Object element){
this.previous=previous;
this.next=next;
this.element=element;
}
}
Vector
Vector向量
Vector底层是用数组实现的List , 在相关的方法上都加了同步检测 , 线程安全,效率低.
选择使用的条件:
(1) 线程安全时:vector
(2) 不存在线程安全时,查找多:ArrayList
(3) 不存在线程安全问题时,增删频繁:LinkedList
4、Map接口(键-值对)
- Map接口的实现类:
(1) HashMap
(2) TreeMap
(3) HashTable
(4) Properties - Map常用方法
- HashMap和HashTable的区别:
(1) HashMap:采用哈希算法实现,Map接口最常用的实现类,查找、修改、删除效率高,线程不安全,允许key或value的值为null;
哈希表:数组+链表
(2) HashTable:相比于HashMap增加了synchronized关键字,确保线程同步,线程安全。不允许key或value的值为null;
- 手动实现HashMap
package com.test;
/**
* @author ZRY
* @version 1.0
*/
public class HashMap01 <K,V>{
Node01[] table;//位桶数组
int size;//存放键值对的个数
public HashMap01(){
table=new Node01[16];//长度为2的整数次幂
}
public V get(K key){
int hash=myHash(key.hashCode(),table.length);
V value=null;//初始值
//开始查找
if (table[hash]!=null){
Node01 temp=table[hash];
while (temp!=null){
if (temp.key.equals(key)){//如果找到了
value= (V) temp.value;
break;
}else {//没找到
temp=temp.next;
}
}
}
return value;
}
@Override
//重写父类的toString方法
public String toString() {
StringBuilder sb=new StringBuilder("{");
for (int i=0;i<table.length;i++){
Node01 temp=table[i];
while (temp!=null){
sb.append(temp.key+":"+temp.value+",");
temp=temp.next;
}
}
sb.setCharAt(sb.length()-1,'}');
return sb.toString();
}
//未扩容
public void put(K key, V value){
//定义新的节点对象
Node01 newNode=new Node01();
newNode.hash=myHash(key.hashCode(),table.length);
newNode.key=key;
newNode.value=value;
newNode.next=null;
Node01 temp=table[newNode.hash];
Node01 iterLast=null;//正在遍历最后一个元素
boolean keyRepeat=false;
if (temp==null){
//此处数组为空,将新节点直接放入
table[newNode.hash]=newNode;
size++;
}else {
//此处数组元素不为空,遍历链表
while (temp!=null){
//判断key如果重复,则覆盖
if (temp.key.equals(key)){
keyRepeat=true;
temp.value=value;//覆盖value
break;
}else{//key不重复
iterLast=temp;
temp=temp.next;
}
}
if (!keyRepeat){//没有重复
iterLast.next=newNode;
size++;
}
}
}
public int myHash(int v,int length){
return v&(length-1);//取模运算
}
}
//-----------------------------------------------
//测试类
class Test{
public static void main(String[] args) {
HashMap01<Integer,String > m=new HashMap01();
m.put(10,"aa");
m.put(20,"bb");
m.put(30,"cc");
m.put(53,"53");
System.out.println(m.toString());
System.out.println(m.get(53));
}
}
//------------------------------------------------
//节点类,用于HashMap01
class Node01<K,V>{
int hash;
K key;
V value;
Node01 next;
}
5、Set接口
set容器的特点:无序,不可重复.
set常用的实现类: HashSet , TreeSet
6、迭代器介绍
- 一般遇到容器判断删除情况时使用迭代器遍历
public class Test {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
for (int i = 0; i < 5; i++) {
set.add("a" + i);
}
System.out.println(set);
for (Iterator<String> iter = set.iterator(); iter.hasNext();) {
String temp = iter.next();
System.out.print(temp + "\t");
}
System.out.println();
System.out.println(set);
}
}
7、遍历集合方法
-
遍历List方法
- 普通for循环
- 增强for循环
- 迭代器
-
遍历Set方法
- 增强for循环
- 迭代器
-
遍历Map方法
- 迭代器
- entrySet
8、Collection工具类
1. void sort(List) //对List容器内的元素排序,排序的规则是按照升序进行排序。
2. void shuffle(List) //对List容器内的元素进行随机排列。
3. void reverse(List) //对List容器内的元素进行逆续排列 。
4. void fill(List, Object) //用一个特定的对象重写整个List容器。
5. int binarySearch(List, Object)//对于顺序的List容器,采用折半查找的方法查找特定对象。
9、练习题
一、选择题
1、以下选项中关于java集合的说法错误的是(AC)
A.List接口和Set接口是Collections接口有两个子接口
B.List接口中存放的元素具有有序,不唯一的特点
C.Set接口中存放的元素具有无序,不唯一的特点
D.Map接口存放的是映射信息,每个元素都是一个键值对
2、如下Java代码,输出的运行结果是( A)。
public class Test {
public static void main(String[ ] args) {
List<String> list=new ArrayList<String>();
list.add("str1");
list.add(2, "str2");
String s=list.get(1);
System.out.println(s);
}
}
A.运行时出现异常
B.正确运行,输出str1
C.正确运行,输出str2
D.编译时出现异常
3、在Java中,下列集合类型可以存储无序、不重复的数据的是( D)。(选择一项)
A.ArrayList
B.LinkedList
C.TreeSet
D.HashSet
4、以下代码的执行结果是( C)。(选择一项)
Set<String> s=new HashSet<String>();
s.add("abc");
s.add("abc");
s.add("abcd");
s.add("ABC");
System.out.println(s.size());
A.1
B.2
C.3
D.4
- 给定如下Java代码,编译运行的结果是( C)。(选择一项)
public class Test {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
String s = "code";
map.put(s, "1");
map.put(s, "2");
System.out.println(map.size());
}
}
A.编译时发生错误
B.运行时引发异常
C.正确运行,输出:1
D.正确运行,输出:2
二、 简答题
-
集合和数组的比较。
- 集合存储的是引用数据类型,大小可变
- 数组存储基本数据类型和引用数据类型,大小固定
-
简述List、Set、Collection、Map的区别和联系。
- List 和Set是Collection的子接口,Collection接口存储无序,不唯一对象
- List接口存储有序,不唯一对象
- Set接口存储无序,唯一对象
- Map接口存储键值对对象,是key和value的映射关系,key唯一
-
ArrayList和LinkedList的区别和联系。它们的底层分别是用什么实现的?
- ArrayList 是数组实现的
- LinkedList是双向链表实现的
-
HashSet采用了哈希表作为存储结构,请说明哈希表的特点和实现原理。
提示:结合Object类的hashCode()和equals()说明其原理。
系统类已经覆盖了hashCode方法,自定义类如果要放入hash类集合,一般会重写hashCode方法和 equals方法,如果不重写,调用的是Object类的hashCode方法和equals方法,而Object类的hashCode方法返回的是对象的地址,equals方法会默认调用==判断对象的地址是否相同。
-
使用泛型有什么好处?
消除强制类型转化,增强代码复用
三、 编码题
-
使用List和Map存放多个图书信息,遍历并输出。其中商品属性:编号,名称,单价,出版社;使用商品编号作为Map中的key。
-
使用HashSet和TreeSet存储多个商品信息,遍历并输出;其中商品属性:编号,名称,单价,出版社;要求向其中添加多个相同的商品,验证集合中元素的唯一性。
提示:向HashSet中添加自定义类的对象信息,需要重写hashCode和equals( )。
向TreeSet中添加自定义类的对象信息,需要实现Comparable接口,指定比较 规则。
-
实现List和Map数据的转换。具体要求如下:
功能1:定义方法public void listToMap( ){ }将List中Student元素封装到Map中
1) 使用构造方法Student(int id,String name,int age,String sex )创建多个学生信息并加入List; 2) 遍历List,输出每个Student信息; 3) 将List中数据放入Map,使用Student的id属性作为key,使用Student对象信息作为value; 4) 遍历Map,输出每个Entry的key和value。
功能2:定义方法public void mapToList( ){ }将Map中Student映射信息封装到List
1) 创建实体类StudentEntry,可以存储Map中每个Entry的信息; 2) 使用构造方法Student(int id,String name,int age,String sex )创建多个学生信息,并使用Student的id属性作为key,存入Map; 3) 创建List对象,每个元素类型是StudentEntry; 4) 将Map中每个Entry信息放入List对象。
功能3:说明Comparable接口的作用,并通过分数来对学生进行排序。