文章目录
泛型
- 泛型的本质就是“数据类型的参数化”。 我们可以把“泛型”理解为数据类型的一个占位符(形式参数),即告诉编译器,在调用泛型时必须传入实际类型
- 泛型参数用一对尖括号<E>表示,使用时需要将类型参数替换为实际的参数
public class TestGeneric {
public static void main(String[] args) {
MyCollection<String> s= new MyCollection<String>();
s.set("宋江",0);
s.set("卢俊义",1);
s.set("吴用",2);
String a = s.get(1);
System.out.println(a);
}
}
//泛型类
class MyCollection<E>{ //E表示泛型
Object[] objs = new Object[5];
public void set(E obj,int index){
objs[index] = obj;
}
public E get(int index){
return (E)objs[index];
}
}
Collection的常用方法
- 容器相关类都定义了泛型,我们在开发和工作中,在使用容器类时都要使用泛型。这样,在容器的存储数据、读取数据时都避免了大量的类型判断。使用容器时要在尖括号中指定类型
- Collection接口的常用方法,方法带All的时候其参数是另一个Collection
import java.util.ArrayList;
import java.util.Collection;
public class TestList {
public static void main(String[] args) {
Collection<String> list = new ArrayList(); //[]
System.out.println("元素个数为:"+list.size()); //元素个数
System.out.println("是否为空:"+list.isEmpty());//是否为空
list.add("唐三藏"); //添加元素
list.add("孙悟空");
list.add("猪八戒");
System.out.println("元素个数为:"+list.size());
System.out.println(list);//[唐三藏, 孙悟空, 猪八戒]
list.remove("孙悟空"); //移除元素,但是元素还在内存中
System.out.println(list);
System.out.println(list.contains("猪八戒")); //是否包含
Object[] objs = list.toArray(); //转换出Object数组
list.clear(); //移除所有元素
System.out.println(list);
//两个Collection之间的操作
Collection<String> list1 = new ArrayList<>();
Collection<String> list2 = new ArrayList<>();
list1.add("孙悟空");
list1.add("杨戬");
list1.add("哪吒");
list2.add("杨戬");
list2.add("玉鼎真人");
list2.add("元始天尊");
//list1.addAll(list2); //两个容器内容串起来,[孙悟空, 杨戬, 哪吒, 杨戬, 玉鼎真人, 元始天尊]
//list1.removeAll(list2);//移除list1中相交的元素[孙悟空, 哪吒]
list1.containsAll(list2); //是否包含list2的所有元素
list1.retainAll(list2); //保留相交元素[杨戬]
System.out.println(list1);
}
}
List
- 有序:List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。
可重复:List允许加入重复的元素。更确切地讲,List通常允许满足 e1.equals(e2) 的元素重复加入容器。 - List常用的ArrayList,LinkedList,Vector。ArrayList底层用数组实现。import java.util.List;
- 与索引相关的操作,插入,移除,修改,获取,
List<Integer> a = new ArrayList<>();
a.add(100);
a.add(200);
a.add(300);
a.add(400);
System.out.println(a);
//指定位置插入元素
a.add(2,700);
System.out.println(a); //100, 200, 700, 300, 400]
//移除指定位置的元素
a.remove(2);
System.out.println(a); //[100, 200, 300, 400]
//修改指定位置的元素
a.set(2,800);
System.out.println(a); //[100, 200, 800, 400]
//获取元素
System.out.println(a.get(2)); //800
- 获取元素的索引,indexOf第一次出现的位置,lastIndexOf最后一次出现的位置
a.add(200);
System.out.println(a);//[100, 200, 800, 400, 200]
System.out.println(a.indexOf(200)); //1
System.out.println(a.lastIndexOf(200));//4
ArrayList
- ArrayList底层是用数组实现的存储。 特点:查询效率高,增删效率低,线程不安全。如果频繁增删,使用LinkedList
- ArrayList的长度不受限制,动态扩容实现。初始化时可以使用默认长度。
- 动态扩容的源码如下,右移一位表示除以2。扩容为原来的1.5倍
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 自定义实现ArrayList的一些功能,size,isEmpty,add,get,set,remove,toString
package MyPro09;
public class MyArrayList<E> {
private Object[] elementData;
private int size;
private static final int DEFAULT_CAPACITY = 10;
//构造器
public MyArrayList(){
elementData = new Object[DEFAULT_CAPACITY];
}
public MyArrayList(int capacity){
if(capacity<0){
throw new RuntimeException("capity is less than zero");
}else if(capacity==0){
elementData = new Object[DEFAULT_CAPACITY];
}else{
elementData = new Object[capacity];
}
}
//获取元素个数的方法size
public int size(){
return size;
}
//是否为空
public boolean isEmpty(){
return size == 0 ? true : false;
}
//add方法
public void add(E element){
//判断是否需要扩容,需要的话就扩容
if(size == elementData.length){
//新数组变为1.5倍长度
Object[] newArray = new Object[elementData.length + (elementData.length >> 1)];
System.arraycopy(elementData,0,newArray,0,elementData.length);
//修改引用地址
elementData = newArray;
}
elementData[size++] = element;
}
//检查索引
private void checkRange(int index){
if(index < 0 || index >= size){
throw new RuntimeException("Index Out Of Bound..");
}
}
//get
public E get(int index){
checkRange(index);
return (E)elementData[index];
}
//set
public void set(E element,int index){
checkRange(index);
elementData[index] = element;
}
//remove,一个可以给定索引或元素
public void remove(int index){
//数组的拷贝,将索引位置之后的元素拷贝到本身
int numMoved = elementData.length-index-1;
if(numMoved>1){
System.arraycopy(elementData,index+1,
elementData,index,numMoved);
}
//将末尾位置的元素设置为null,同时size减去1
elementData[--size] = null;
}
//移除给定元素,E 不能是基本数据类型,不会和index冲突
public void remove(E element){
for(int i = 0;i<size;i++){
//容器中的比较用equals方法,不要用两个等号==
if(element.equals(get(i))){
remove(i); //调用移除index的方法
}
}
}
//toString方法
public String toString(){
if(size == 0){
return "[]";
}
StringBuilder sb = new StringBuilder();
sb.append("[");
for(int i = 0;i<size;i++){
sb.append(elementData[i]+",");
}
sb.setCharAt(sb.length()-1,']');
return sb.toString();
}
}
LinkedList
- LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。
- 双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。 所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点
- 自定义实现LinkedList
public class MyLinkedList<E> {
public static void main(String[] args) {
MyLinkedList<String> list = new MyLinkedList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
System.out.println(list);
System.out.println(list.get(2));
list.remove(5);
System.out.println(list);
list.add(2,"haha");
System.out.println(list);
}
private Node first;
private Node last;
private int size;
public void add(E element){
Node node = new Node(element);
//如果开头是个空list,那么fist和last都是node自己
if(first == null){
first = node;
last = node;
}else{
//如果已经有存在的list
node.previous = last; //前一个节点是已存在的最后一个节点
node.next = null; //下一个节点是空,因为是插入到最后
last.next = node; //last的next设置为当前
last = node; //last设置为node
}
size++;
}
//insert
public void add(int index,E obj){
checkRange(index);
Node newNode = new Node(obj);
Node temp = getNode(index);
if(temp != null){
Node up = temp.previous;
up.next = newNode;
newNode.previous = up;
newNode.next = temp;
temp.previous = newNode;
}
}
public E get(int index){
checkRange(index);
Node temp = getNode(index);
return temp != null ? (E)temp.element : null;
}
//remove
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 = up;
}
size--;
}
}
//根据索引获取节点,便于重复使用
public Node getNode(int index){
Node temp = first;
if(index <(size>>1)){
//从前往后遍历
for(int i = 0;i<index;i++){
temp = temp.next;
}
}else{
temp = last;
//从后往前遍历
for(int i = size-1;i>index;i--){
temp = temp.previous;
}
}
return temp;
}
@Override
public String toString() {
if(first == null){
return "[]";
}
StringBuilder sb = new StringBuilder("[");
Node temp = first;
while (temp != null){
//System.out.println(temp.element);
sb.append(temp.element+",");
temp = temp.next;
}
sb.setCharAt(sb.length()-1,']');
return sb.toString();
}
//检查索引
private void checkRange(int index){
if(index < 0 || index >= size){
throw new RuntimeException("Index Out Of Bound..");
}
}
}
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底层是用数组实现的List,相关的方法都加了同步检查,因此“线程安全,效率低”。 比如,indexOf方法就增加了synchronized同步标记。线程安全的ArrayList
Map
- Map就是用来存储“键(key)-值(value) 对”的。 Map类中存储的“键值对”通过键来标识
- Map 接口的实现类有HashMap、TreeMap、HashTable、Properties等
- 常用方法
package MyPro09;
import java.util.HashMap;
import java.util.Map;
public class TestMap {
public static void main(String[] args) {
Map<Integer,String> m1 = new HashMap<>();
m1.put(1,"宋江");
m1.put(2,"卢俊义");
System.out.println(m1);
System.out.println(m1.get(2)); //获取键对应的value
System.out.println("元素个数:"+m1.size());
System.out.println("是否为空:"+m1.isEmpty());
System.out.println("是否包含某个key:"+m1.containsKey(1));
System.out.println("是否包含某个value:"+m1.containsValue("呼延灼"));
Map<Integer,String> m2 = new HashMap<>();
m2.put(3,"林冲");
m2.put(4,"公孙胜");
m1.putAll(m2);
System.out.println(m1);
m1.put(3,"吴用"); //键重复会更新value
System.out.println(m1);
}
}
- Map中存放自定义的对象
import java.util.HashMap;
import java.util.Map;
public class TestMap2 {
public static void main(String[] args) {
Haohan h1 = new Haohan(1,"宋江");
Haohan h2 = new Haohan(1,"卢俊义");
Map<Integer,Haohan> s = new HashMap<>();
s.put(1,h1);
s.put(2,h2);
Haohan t1 = s.get(1);
System.out.println(t1);
}
}
class Haohan{
int id;
String name;
public Haohan(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return String.format("id:%s,name:%s",id,name);
}
}
HashMap与HashTable的区别
1. HashMap: 线程不安全,效率高。允许key或value为null。
2. HashTable: 线程安全,效率低。不允许key或value为null
HashMap底层
-
HashMap底层实现采用了哈希表,哈希表的基本结构就是“数组+链表”
-
https://www.sxt.cn/Java_jQuery_in_action/nine-hashmap-bottom.html
-
Entry[] table 就是HashMap的核心数组结构,我们也称之为“位桶数组”。
-
每一个Entry对象就是一个单向链表结构, 一个Entry对象存储了:
- key:键对象 value:值对象
- next:下一个节点
- hash: 键对象的hash值
-
自定义实现
public class SxtHashMap04<K,V> {
Node3[] table; //位桶数组。bucket array
int size; //存放的键值对的个数
public SxtHashMap04() {
table = new Node3[16]; //长度一般定义成2的整数幂
}
public V get(K key){
int hash = myHash(key.hashCode(), table.length);
V value = null;
if(table[hash]!=null){
Node3 temp = table[hash];
while(temp!=null){
if(temp.key.equals(key)){ //如果相等,则说明找到了键值对,返回相应的value
value = (V)temp.value;
break;
}else{
temp = temp.next;
}
}
}
return value;
}
public void put(K key, V value){
//如果要完善,还需要考虑数组扩容的问题!!!
//定义了新的节点对象
Node3 newNode = new Node3();
newNode.hash = myHash(key.hashCode(),table.length);
newNode.key = key;
newNode.value = value;
newNode.next = null;
Node3 temp = table[newNode.hash];
Node3 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即可。其他的值(hash,key,next)保持不变。
break;
}else{
//key不重复,则遍历下一个。
iterLast = temp;
temp = temp.next;
}
}
if(!keyRepeat){ //没有发生key重复的情况,则添加到链表最后。
iterLast.next = newNode;
size++;
}
}
}
@Override
public String toString() {
//{10:aa,20:bb}
StringBuilder sb = new StringBuilder("{");
//遍历bucket数组
for(int i=0;i<table.length;i++){
Node3 temp = table[i];
//遍历链表
while(temp!=null){
sb.append(temp.key+":"+temp.value+",");
temp = temp.next;
}
}
sb.setCharAt(sb.length()-1, '}');
return sb.toString();
}
public static void main(String[] args) {
SxtHashMap04<Integer,String> m = new SxtHashMap04<>();
m.put(10, "aa");
m.put(20, "bb");
System.out.println(m.get(85));
}
public static int myHash(int v, int length){
// System.out.println("hash in myHash:"+(v&(length-1))); //直接位运算,效率高
// System.out.println("hash in myHash:"+(v%(length-1))); //取模运算,效率低
return v&(length-1);
}
}
class Node3<K,V> {
int hash;
K key;
V value;
Node3 next;
}
TreeMap
- 需要排序的时候用TreeMap,底层是红黑二叉树
- 实现不同对象的比较需要实现Comparable接口,实现compareTo方法,返回正数,0,负数,对应大于,等于,小于
import java.util.Map;
import java.util.TreeMap;
public class TestTreeMap {
public static void main(String[] args) {
Map<Integer,String> tm = new TreeMap<>();
tm.put(1,"one");
tm.put(2,"two");
tm.put(3,"three");
//按照key递增的方式排序
for(Integer key:tm.keySet()){
System.out.println(key+"---"+tm.get(key));
}
Map<Emoloyee,String> tm2 = new TreeMap<>();
tm2.put(new Emoloyee(1,"a",400),"a");
tm2.put(new Emoloyee(2,"b",300),"b");
tm2.put(new Emoloyee(4,"c",200),"c");
tm2.put(new Emoloyee(3,"d",200),"d");
//按照salary递增的方式
for(Emoloyee key:tm2.keySet()){
System.out.println(key+"---"+tm2.get(key));
}
}
}
//自定义类实现比较功能需要实现Comparable<T>接口
class Emoloyee implements Comparable<Emoloyee>{
int id;
String name;
double salary;
public Emoloyee(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public int compareTo(Emoloyee o) {
//大于返回正数,等于返回0,小于返回负数
if(this.salary>o.salary){
return 1;
}else if(this.salary<o.salary){
return -1;
}else{
if(this.id>o.id){
return 1;
}else if(this.id < o.id){
return -1;
}else{
return 0;
}
}
}
@Override
public String toString() {
return "id:"+id+",salary:"+salary+",name:"+name;
}
}
Set
- Set接口继承自Collection,Set接口中没有新增方法,方法和Collection保持完全一致。我们在前面通过List学习的方法,在Set中仍然适用
- Set容器特点:无序、不可重复。无序指Set中的元素没有索引,我们只能遍历查找;不可重复指不允许加入重复的元素。更确切地讲,新元素如果和Set中某个元素通过equals()方法对比为true,则不能加入;甚至,Set中也只能放入一个null元素,不能多个。
- Set常用的实现类有:HashSet、TreeSet等,我们一般使用HashSet。
HashSet
- 基本使用
import java.util.HashSet;
import java.util.Set;
public class TestSet {
public static void main(String[] args) {
Set<Integer> s = new HashSet<>();
s.add(3);
s.add(2);
s.add(2);
System.out.println(s);
s.remove(2);
System.out.println(s);
s.isEmpty();
}
}
- HashSet是采用哈希算法实现,底层实际是用HashMap实现的(HashSet本质就是一个简化版的HashMap。map为HashMap<E,Object> map
- 查询效率和增删效率都比较高
- 看add()方法,发现增加一个元素说白了就是在map中增加一个键值对,键对象就是这个元素,值对象是名为PRESENT的Object对象
- 自定义简易的HashSet
import java.util.HashMap;
public class MyHashSet {
public static void main(String[] args) {
MyHashSet mmp = new MyHashSet();
mmp.add("d");
mmp.add("b");
mmp.add("c");
System.out.println(mmp);
}
HashMap map;
private static final Object PRESENT = new Object();
public MyHashSet(){
map = new HashMap();
}
public void add(Object o){
map.put(o,PRESENT);
}
public int size(){
return map.size();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for(Object key : map.keySet()){
sb.append(key+",");
}
sb.setCharAt(sb.length()-1,']');
return sb.toString();
}
}
TreeSet
- TreeSet底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。 TreeSet内部需要对存储的元素进行排序,因此,我们对应的类需要实现Comparable接口。这样,才能根据compareTo()方法比较对象之间的大小,才能进行内部排序
import java.util.Set;
import java.util.TreeSet;
public class TestTreeSet {
public static void main(String[] args) {
Set<Integer> t = new TreeSet<>();
t.add(300);
t.add(200);
t.add(100);
System.out.println(t); //[100, 200, 300] 自动排好序
//Employee实现了Comparable接口,打印时会排序
Set<Emoloyee> e = new TreeSet<>();
e.add(new Emoloyee(1,"a",400));
e.add(new Emoloyee(2,"b",300));
e.add(new Emoloyee(4,"c",200));
e.add(new Emoloyee(3,"d",200));
System.out.println(e);
}
}
迭代器
遍历List
public static void TestList(){
List<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
//while遍历
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//for遍历
for(Iterator<String> iter = list.iterator();iter.hasNext();){
String temp = iter.next();
System.out.println(temp);
}
}
遍历Set
public static void TestSet(){
Set<String> s = new HashSet<>();
s.add("qq");
s.add("ee");
s.add("rr");
//while遍历
Iterator<String> iterator = s.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//for遍历
for(Iterator<String> iter = s.iterator();iter.hasNext();){
String temp = iter.next();
System.out.println(temp);
}
}
遍历Map
//遍历Map的方式1,直接获取键值对
public static void TestMap1(){
Map<Integer,String> m = new HashMap<>();
m.put(1,"aa");
m.put(2,"bb");
m.put(3,"cc");
Set<Map.Entry<Integer,String>> entries = m.entrySet();
for(Iterator<Map.Entry<Integer,String>> iter = entries.iterator();iter.hasNext();){
Map.Entry<Integer,String> temp = iter.next();
System.out.println(temp.getKey()+"->"+temp.getValue());
}
}
//遍历Map的方式2,先获取键的集合,再遍历
public static void TestMap2(){
Map<Integer,String> m = new HashMap<>();
m.put(1,"aa");
m.put(2,"bb");
m.put(3,"cc");
Set<Integer> keySet = m.keySet();
for(Iterator<Integer> iter = keySet.iterator() ;iter.hasNext();){
Integer temp = iter.next();
System.out.println(temp+"-->"+m.get(temp));
}
}
遍历方式总结
- https://www.sxt.cn/Java_jQuery_in_action/nine-ergodicset.html
- List:通过索引for循环,增强for循环,for循环迭代器,while循环迭代器
- Set: 增强for循环,for循环迭代器,while循环迭代器
- Map: (1)先获取key的Set,然后同Set的遍历方式,(2)迭代器entrySet()直接获取键值对
Collections工具类
- java.util.Collections 提供了对Set、List、Map进行排序、填充、查找元素的辅助方法
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TestCollections {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for(int i = 0;i<10;i++){
list.add("abc"+i);
}
System.out.println(list);
//打乱顺序
Collections.shuffle(list);
System.out.println(list);
//反转列表
Collections.reverse(list);
System.out.println(list);
//排序,从小到大,反过来的话再reverse一下
//自定义类需要继承Comparable接口
Collections.sort(list);
System.out.println(list);
//查找,没有查到就返回负数
System.out.println(Collections.binarySearch(list,"abc15"));
}
}
容器存储表格
- 思路1,每行数据用map表示,表头为键,内容为值,所有的map放到一个list中。
- 思路2,每行数据用一个类表示,类的属性为表头,所有的类放到一个map或list中
- 思路2的实现
import java.util.*;
public class TestStoreTable {
public static void main(String[] args) {
User u1 = new User(1001,"A",100,"2020-01-20");
User u2 = new User(1002,"B",200,"2019-01-20");
User u3 = new User(1003,"C",300,"2018-01-20");
//数据存入list,使用Ctrl+D快速复制粘贴上一行
List<User> list = new ArrayList<>();
list.add(u1);
list.add(u2);
list.add(u3);
for(User u:list){
System.out.println(u);
}
System.out.println("--------------");
//数据存入map
Map<Integer,User> map = new HashMap<>();
map.put(u1.getId(),u1);
map.put(u2.getId(),u2);
map.put(u3.getId(),u3);
Set<Integer> keys = map.keySet();
for(Integer key:keys){
System.out.println(map.get(key));
}
}
}
class User {
/*
User类,javabean
*/
private int id;
private String name;
private double salary;
private String hiredate;
//一个完整的javabean。要有set和get方法,以及无参构造器!
public User() {
}
public User(int id, String name, double salary, String hiredate) {
super();
this.id = id;
this.name = name;
this.salary = salary;
this.hiredate = hiredate;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getHiredate() {
return hiredate;
}
public void setHiredate(String hiredate) {
this.hiredate = hiredate;
}
@Override
public String toString() {
return "id:"+id+",name:"+name+",salary:"+salary+",hiredate:"+hiredate;
}
}