我们在接收新的技术的时候怎么接受他的好 ?
1:性能方面 改变比较大
2:使用方面比较简单(如果新的性能高十分麻烦也会用新的数据)
此时我们来恶心一下自己,来改一下数组实现和容器差不多的功能有多麻烦,然后我们在来接触集合,那么我们首先来模拟一下ArrayList的底层操作
模拟ArrayList
作用:计算机存储组织数据的方式,然后就是模拟数据进行增删改查
模拟ArrayList的增删改查
先初始化数组一下 然后对集合数组进行增删改查操作
public class PlayerList{
//存储球员号码的一个Integer集合
private Integer[] player;
//记录球员在场人数
private int size = 0;
//使用构造器在创建对象的时候初始化容量的
public PlayerList(int initialCapacity){
//判断传入的初始值不能为负数否则抛出异常
if(initialCapacity<0){
throw new RuntimeException("初始容量不能为负数")
}
//初始化数组容量
this.palyers = new Integer[initialCapacity];
}
public PlayerList(){
//创建一个默认容量的数组
//调用其他构造器完成数组的初始化
this(10);选择
}
}
提供多个构造器的目的:满足更多需求以满足各种不同的用户 并且可以更灵活的初始化容器的数量
此时此刻我们已经完成了初始化的操作,那么我们要开始造轮子了,轮子emmm就是增删改查以及一个打印操作
增加操作—有扩容版本
//此时向数组里面添加一个数字 代表上场一个球员
public void add(Sintger playerNumber){
//判断此时容器满没满,如果此时容器已经满了,就需要扩容,
if(size==player.length){
//扩容机制为原来的二倍
this.players = arrays.copyOf(players,size*2)
}
//此时判断完毕之后就开始赋值保存操作
this.player[size] = playerNumber;//保存球衣号码
size++;//球员上场+1
//到最后一个数组索引的时候还是会加1 所以上边直接就是==了
}
删除操作–
删除的原理:删除之后把后续的元素向前挪动一下,然后把最后一个赋值为null那么就是最初还没在堆中获取的内存空间时的状态
//删除只当位置的球员号码
public void remove(int index){
//判断数组索是否越界 如果越界就抛出异常
if(index<0||index>size){
throw new RunntimeException("数组索引越界");
}
//使用for循环将指定元素 让他的下一位覆盖
for(int i = index;i<size-1;i++){
palyers[i] = players[i+1];
}
//此时将该数组最后一个赋值为空 打印的时候就不会出现了
player[siez-1] = null;
//少一个球员suze需要减1
size--;
}
修改操作
其实就是判断一下索引越界没 然后替换就好
//替换指定位置的球员号码
public void set(int index, Integer newPlayerNumber) {
//判断索引是否越界
if (index < 0 || index >= size) {
throw new RuntimeException("索引越界");
}
//索引替换操作
players[index] = newPlayerNumber;
}
查询操作
原理:判断索引是否越界 然后返回指定索引
//查询操作
public Integer get(int index) {
//判断查询索引是否越界
if (index < 0 || index >= size) {
throw new RuntimeException("索引越界");
}
return players[index];
}
打印操作
原理: 首先判断一下要是数组为空直接返回 null 要是长度为0 返回[] 然后接下来就是高效率不带同步锁的字符串拼接了
public String toString() {
if (players == null) {//如果没有初始化容器
return "null";
}
if (size == 0) {//如果容器中球员数量为0
return "[]";
}
StringBuilder sb = new StringBuilder(40);
sb.append("[");
//判断拼接操作
for(int index = 0;index<size;index++){
sb.append(players[index]);
//如果不是最后一个那么久拼接","否则 "]"
if(index!=size-1){
sb.append(",");
}else{
sb.appends("]");
}
}
//返回打印有球员的位置 使用return的时候我们可以返回处了空的元素
return sb.toString();
}
要是想让该数组接收所有的类型把Integer改为Object类型就可以
出现的问题:无法判断具体的类型,拿出来用的时候就没办法判断是谁
数据性能分析
如果一个算法,要是时间复杂度很高,证明此时这个算法就不是很好,需要我们去优化,是我们衡量我们的代码的时间复杂度的一个东西
其他数据结构
链表:
1:单项链表->从头到尾/从尾到头 a中有无a
2:双向链表->从头到尾,从尾到头 c中有a a中有c
通过引用来表示上一个节点和下一个节点的关系.
查询慢的很 一个一个查
但是在头尾删除和添加十分方便
以面向对象的思想去理解 每个对象都是一个链表中的元素
结论:
查询,修改 ArrayList快 底层数组
擅长头尾操作LinkdList 底层链表
3:队列
只允许在表的两端操作 一头进入(尾) 一头出去(头)
先添加来的先被删除
4:栈 先进后出 但是只能在栈顶做操作 进出都是一样
泛型
//注意:泛型可以使用任意字母表示,也可以使用多个泛型参数
//定义多个 public class point2<T,E,K,V>
//建议使用一个字母
//T: type 类型
//E: emement 元素
//K: key,键
//V: value,值
//做到见名知意呢
用了泛型之后终极使用方法
如果说一个定义了泛型的类,当我们创建集合的时候就该赋予类型了,赋予之后我们之前定义的泛型的类,就确定了类型,不能改变了;
如果用集合的时候出现了强转,一定要想到泛型
List<String> list1 = new ArrayList<>();
//简写 注意此时List集合的泛型为String
那么如果此时有一个User类 需要插入进集合 我们应该怎么定义泛型
List<User<user自己的泛型>> //这么定义久代表已经确定了泛型的类型
那么在接下来的遍历,调用User对象的时候久不需要强行转换了
啰嗦两句:
在用泛型写集合的时候注意集合的泛型是你选择对象的类 加上对象类的泛型,如果你想加一个类并且做出改变的时候不能直接给集合加泛型,感觉这里是一个比较容易混淆的点 再有就是泛型其实就是一个语法糖 让我们省去了许多重复的代码
List和Set以及Map集合
目的作用;其实集合框架就是对这些数据结构进行封装,然后用的时候调用方法就可以,使我们的开发效率更高
根据容器的特点,分为三种类型集合
一:List集合
内部可以添加顺序,也可以元素重复,list常用方法,常用的增删改查
1:add();添加任何元素 添加在尾部 也可以添加在指定索引
2:addAll();将一个List集合全部添加进入当前集合
3:remove();可以移除指定索引 也可以是指定元素
4:removeAll();移除当前集合内包含的所有另一个集合元素
5:set();指定索引,修改成指定元素
6:size();返回元素个数
7:isEmpty();判断元素中元素个数是否为0
8:toArray();将列表转为Object数组
9:contains();判断列表中是否存在指定对象
linked里面也有一些自己的方法,常用的增删改查 因为linked底层是链表格式的,所以对头尾的操作十分擅长 接下来就是介绍一些常用的头尾操作,当然linked属于List类List集合的常用方法也是可以用的,但是查询效率没有ArrayList高 链表里面就是一堆节点一堆lode对象
1:addFirst();将指定元素插入到头
2:addLast();插入到尾部
3:getFirst();查询头部
4:getLast();查询尾部
6:peekFirst()获取但不移除此列表的第一个元素
7:peekLast()获取最后一个元素但是不移除
//通过构造器将一个集合转化为List集合
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
linkedList的常用方法 大多数都是一个First一个Last只要记住一些List的常用方法往上套基本上久差不多了
总结:如果要用头尾操作,那么我们最好选用LinkedList 如果不操作头尾我们最好选用ArrayList
二:Set集合
,内部无顺序,不允许元素重复
常用方法:
add();增加元素
remove();直接移除元素没有索引
没有更改 除非 先删除在添加(自己骗自己)
获取元素就使用迭代器来获取
Set常用的接口有俩一个是HashSet一个是TreeSet,这俩一个是采用哈希实现底层的,一个是使用红黑树实现底层的
HashSet.他是通过先判断HashCode值看看位置上有没有值,要是没有就直接放在那里,入股有就来比较两个对象是否相等那么进入到下一步判断
如果用equals比较相等 如果为true 就视为一个对像
如果用equals比较不等那么就在用哈希碰撞比较,然后存储在之前对象的同位槽上的链表但是此时会降低hash的性能
如果hashCode值不等直接插入到对应的hashCode值那里
TreeSet的树的比较原理就是出入一个数值 如果大于就在右边,如果小于就在左边,无限延展
在树缺省的时候会有默认的比较规则,此时要求元素对象必须实现java.util.Comparable,Comparable这是一个接口然后传入元素的时会自动调用compareTo方法 如果返回1那么就是当前值比传入值优先级高,如果是-1就是低,如果是0那么就是相等;
假如此时我们打算比较User类那么User此时就必须实现java.lang.Comparable的接口,然后实现他的compareTo接
class User implements java.lang.Comparable<User> {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
//默认提供getset方法
}
//比较规则
public int compareTo(User o) {
if (this.age > o.age) {
return 1;
} else if (this.age < o.age) {
return -1;
}
return 0;
}
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
然后我么在调用TreeSet集合的时候每一次添加User操作的是时候就会调用里面的比较方法
我们试一试自定义的方法,其实差别就是我们需要在构建TreeSet对象的时候传入java.utilCompartor接口的实现类对象,Comparator表示比较器内部有封装的方法
自定义方法的时候我们也不需要在比较的对象那里实现接口,只需要在java.utilCompartor的实现类里面传入需要比较的对象作为接口的泛型就好了此时比价的规则不是comparTo了 变为compar了 可能就是为了区分一下
//先看一下接口的实现类如何定义
//名字长度比较
class NameLengthComparator implements java.util.Comparator<User> {
//定义实现接口的比较规则
public int compare(User o1, User o2) {
int ret = o1.getName().length() - o2.getName().length();
//一个主导,一个当主导相同在继续下一步判断
if (ret > 0) {
return 1;
} else if (ret < 0) {
return -1;
} else {
if (o1.getAge() > o2.getAge()) {
return 1;
} else if (o1.getAge() < o2.getAge()) {
return -1;
} else {
return 0;
}
}
}
}
//如何在创建TreeList类中调用这个比较的接口 只需要形式参数传入此类对象就好
public class App {
public static void main(String[] args) {
//这里new了一个名字长度的对象
Set<User> set = new TreeSet<>(new NameLengthComparator());
set.add(new User("James", 30));
set.add(new User("Bryant", 22));
set.add(new User("Allen", 28));
set.add(new User("Will", 17));
System.out.println(set);//
}
//最底下就是一个正常的类 一键生成那种
class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
红黑树的排序就是有一点像二分法查找 每次都会砍掉一般,然后tree底层就是使用红黑树的算法
三:Map集合
作用:可以做到一些set和list办不到的事情, 可以单独拿到key也可以单独拿到value,也可以拿到key对应的value
内部就是一个key对应一个value
key:无顺序,不让重复 就像set那样
value:和key一一对应,然后可以重复那种
其实就像是一个钥匙配一把锁,然后钥匙必须不一样,锁可以一样也可以不一样
Map中常用的方法
put(); 存储一个键值对到Map
putAll(Map m);把集合m中的键值对 加入到当前集合中
remove();直接填入key value就一起删除了
修改方法:(–
可以直接使用put然后覆盖原来的就可以,map修改方法除了通过相同key,使用put铺盖value值,还有另外的方法replace(K key,V value),replace(K key,V oldValue,V newValue)----)
size();返回当前Map中的键值对个数
isEmpty();判断当前集合中键值对(元素)个数是否为0
get()直接填入key就可以返回value的值 如果无key对应null
containsKey();判断当前集合中是否包含指定的key
containsValue()判断Map中是否包含指定的value
Set keyset();返回Map中所有key所组成的Set集合
Collection values();返回当前集合中所有的value成一个集合
set entrySet();返回键值对中所有set的集合
这三个集合就是Map的遍历方法
Map的原理就是通过一个东西找到另一个东西,
public class HashMapDemo1{
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("girl1", "西施");
map.put("girl2", "王昭君");
map.put("girl3", "貂蝉");
map.put("girl4", "杨玉环");
System.out.println("map中有多少键值对:"+map.size());
System.out.println(map);
System.out.println("是否包含key为girl1:"+map.containsKey("girl1"));
System.out.println("是否包含value为貂蝉:"+map.containsValue("貂蝉"));
//替换key为girl3的value值
map.put("girl3", "小乔");
System.out.println(map);
//删除key为girl3的键值对
map.remove("girl3");
System.out.println(map);
}
}
集合的迭代遍历
ArrayList集合遍历三种方式
1:for循环遍历
普通的for循环删除元素可以,但是可能存在删除不完
建议:
1:再成功删除后不执行索引的自增操作
2:倒着遍历元素
List<String> list = new ArrayList<>();
list.add("西施");
list.add("王昭君");
list.add("貂蝉");
list.add("杨玉环");
最基本的方式 和以前的是一样的
//此时循环遍历我们可以 进行删除和添加
//但是会删除不完
//数组我们再删除的时候会删除之后后面的元素会向前移动
解决
1:删除之后就不用++了
2:倒着遍历不会落下元素
for (int index = 0; index < list.size(); index++) {
String ele = list.get(index);
System.out.println(ele);
}
2:foreach foreach迭代遍历 底层使用的是Iterator
删除倒数第二个元素如何实现的是如何实现不报错的
原理:
因为foreach底层使用的是Iterator迭代器遍历
然后curse就是遍历的次数,size就是集合的元素个数
此时遍历删除就是调用hashNext的判断 cursor ! = size; 如果这个值为false就会直接返回 然后此时就不会进入next的判断 如果不动倒数第二个元素,加入动第一个 此时cursor为0 然后size = 3 执行之后 course变为1 size = 2 不等于直接就返回false就好了
然后我们举例子,就是删除倒数第二个
举个例个:三个元素索引 012 size == 3 删除索引为1的时候删除后cursor 变为2(因为遍历到2次了) size会减一 此时curse = size =2 此时curse已经到了最后一个了 此时遍历就结束了就不会执行到next里面了跳出了循环然后就不会抛出那个异常了
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String ele = it.next();
System.out.println(ele);
}
3:Iterator迭代器遍历
list内remove 只修改modCount 但是没有修改预期的值 modCount和expectedModCoutn两个值不同步所以不同步
modCount每次成功删除后都要记录修改的次数 添加也会记录
如果想要成功匹配我们就要使用迭代器的remo方法
迭代器的remove会删除当前迭代到的元素 指向是谁就删除谁
也会先检查一次 然后让modeCount=expectedModCount相等
此时这样才会让这两个元素相等 此时我们就可以达到正确的
删除效果
语法: hashNext()判断有没有下一位元素且下一个元素不为null,
next;取到下一位元素指针移动一位
注意:如果不做泛型那么迭代改变的时候就需要强转
//底层
通过变量的增加给我们确定是否还有元素
public boolean hasNext() {
return next != size;
}
1:获取到指定结合的迭代器
Iterator<这里可以有泛型> it = list.Iterator();
while(it.hashNext()){
it.next(); //此时就获取了遍历的对象 可以进一步操作
}
总结:如果我们要一 边迭代一遍修改 就使用迭代器
foreach比较快 和方便
集合遍历的两种方式
遍历方法
Iterator | foreach
Map集合的迭代遍历方法
Map迭代遍历有三种特有的方法,然后需要注意的就是,我们将所有的键值对返回成为一个set集合的时候他的类型是Entry<内部就key和value的类型> 迭代的时候用的是Entry里面的getKey和getValue方法 并不是Map中的方法
//获取Map中所有的key
Set<String> keys = map.keySet();
System.out.println("Map中所有key:"+keys);
//获取Map中所有的value
Collection<String> values = map.values();
System.out.println("Map中所有value:"+values);
//获取Map中所有的key-value(键值对)
Set<Entry<String, String>> entrys = map.entrySet();
for (Entry<String, String> entry : entrys) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"->"+value);
}
TreeMap底层基于红黑树算法,因为Map中的key是Set,所以不能保证添加的先后顺序,也不允许重复,但是Map中存储的key会默认使用自然排序(从小到大),和TreeSet一样,除了可以使用自然排序也可以自定义排序。
public class App {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("girl4", "杨玉环");
map.put("girl2", "王昭君");
map.put("key1", "西施");
map.put("key3", "貂蝉");
System.out.println(map);
//-------------------------------------------
map = new TreeMap<>(map);
System.out.println(map);
}
}
Collection的常用方法
void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。
boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int frequency(Collection c, Object o)//统计元素出现次数
演示一个用法
// void sort(List list),按自然排序的升序排序
Collections.sort(arrayList);
System.out.println("Collections.sort(arrayList):");
System.out.println(arrayList);