java 数组,集合,字符串中常用的方法

数组Arrays 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Arrays;

Arrays.toString() //输出数组的内容

Arrays.sort() //快速排序

Arrays.equals() //逐个比较数组元素是否相等

Arrays.binarySearch( c数组,key定值) //在数组中寻找定值,返回此值的下标

Arrays.copyOf() //拷贝数组

Arrays.copyOfRange(arr,from A,to B) //拷贝AB 之间的数组的个数

字符串String类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int length();
char charAt();//提取字符串中指定位置的值
char toCharArray();//将字符串转变为字符数组
String trim(); //返回字符串,忽略前导空白和尾部空白
String toUpperCase();
String toLowerCase();
boolean isEmptyt();//判断是否为空字符串
boolean equalsIgnoreCase(String s); //忽略大小比较字符串的大小
int compareTo(String s); //比较两个字符串的大小,相等0,不相等-1
String substring(int beginIndex); //返回新字符串,从指定位置开始截取到最有一个位置
String subString(int begin ,int end);//截取左闭右开的字符串
boolean contains(String str);// str.contains(str1);str中是否包含str1
int indexOf(String str) //返回字符串在此字符串中第一次出现的索引,没有返回-1
indexOf(String str,int index);//index之后开始索引查找
String replace(char oldChar,newChar);//替换字符串
boolean matches(String str);//匹配是否符合正则表达式
String [] split("字符");//根据给定的正则表达式拆分字符串,形成一个新的String数组


1
2
3
4
5
6
7
8
9
10
11
12
StringBuffer
线程安全: StringBuffer 是线程安全的,因此适用于多线程环境,可以确保在并发操作时不会出现数据不一致的问题。
同步方法: StringBuffer 的方法都是同步的,这会带来一些性能上的损失。因此,当需要在线程安全的环境中进行字符串操作时,使用 StringBuffer 是合适的选择。

StringBuilder JDK1.5版本之后引入的
非线程安全: StringBuilder 不是线程安全的,因此在单线程环境中使用更为高效。

性能优势: 由于不需要考虑线程安全性,StringBuilder 在性能上通常比 StringBuffer 更优秀。在单线程环境中进行大量字符串操作时,使用 StringBuilder 可以提高效率。

选择场景:
多线程环境: 如果在多线程环境中需要进行字符串操作,选择 StringBuffer。
单线程环境: 如果在单线程环境中进行字符串操作,选择 StringBuilder 可以获得更好的性能。

StringBuilder的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
StringBuilder str = new StringBuilder("helloworld");
(int capacity);
(空参构造器)//默认初始为16个字符
str.append(String str);//添加各种数据
str.delete(int start,int end);//删除指定位置的内容
deletecharAt(int index);//删除指定索引位置上的元素
str.replace(int start,int end,String str);//指定位置替换
str.insert(int start,String s);//指定位置插入
str.reverse();//当前字符翻转
public int indexOf(String str) //str在字符串中首次出现的位置
public String substring(int start,int end)
public void setCharAt(int n,char ch) //将指定位置的字符换为新的

集合框架:collection接口继承树

集合框架:

Collection接口:单列集合,用来存储一个一个的对象

list接口:存储有序的,可以重复的数据 –>”动态数组”

​ ArrayList: list的主要实现类,线程不安全。底层使用object[]数组存储

​ LinkedList:底层使用双向链表存储。对于频繁的插入和删除操作,效率高

​ Vector:古老实现类,线程安全,效率低

set接口:无序的,不可重复的数据 –>高中数学中的集合

1
2
3
      无序性,不等于随机性,存储数据根据数据计算哈希值,根据哈希值存储

​ 不可重复性:靠存储的元素类型是否重写hashcode()和equals()方法实现的,比较过程:存储元素会使用hash()算法生成一个int类型的hashcode散列值,然后与以存储的元素的hashcode比较,如果不一致则是新的对象,如果一致的话,再调用equals()方法,比较两个对象的内容是否相等,这样就确保了存储的唯一性。

​ HashSet:set接口的主要实现类,线程不安全,可以存储null值,底层使用哈希表

​ LinkedHashSet:HashSet的子类,可以按照添加的顺序遍历,底层使用哈希表和链表,频繁遍历效率高

​ TreeSet:底层二叉树红黑树,可以按照添加的对象的指定属性,排序,所以只能添加同类的对象

        TreeSet底层数据结构采用二叉树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造),自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;比较器排需要在TreeSet初始化是时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法;

image-20240109193220620

Map接口:双列集合,用来存储一对(key,value)的数据 ,底层数组+链表+红黑树

​ HashMap:主要实现类。线程不安全,效率高,可以存储null,

​ LinkedHashMap:底层链表,保证在遍历元素时候,可以按照添加的顺序遍历,频繁的遍历效率高

​ WeakHashMap:

​ Hashtable:古老实现类。线程安全,效率低,不能存储null

               properties:常用来处理配置文件,key-value都是String类型的

​ TreeMap:底层红黑树,保证添加的key进行排序(key必须是同一类对象)实现排序遍历

image-20240109152948047

map中的key:无序的,不可重复的,使用set存储所有的key

​ value:无序的,可重复的

map的底层实现原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
JDK8之后map底层是  数组+链表+红黑树
当首次执行执行插入操作map.put(key,value)后,底层创建一个长度为16的数组
key相当于set不可重复,调用key1类所在的hashcode()计算哈希值,不一样,根据hash值计算出存储在数组中的位置,
若存放位置为空的话,则添加成功
若存放位置的数据不为空,(说明此位置已经有一个或者多个数据了,以链表的形式存在),比较key1和已有元素的hash值,如果都不相同,则添加成功,
如果相同,则继续比较key1类所在的equals()方法,判断是否为相同的元素,
如果不相同,则直接添加
如果相同,则使用新的的value值,替换之前的value值

当使用hashcode()计算出的数组的一个索引位置上的元素以链表的形式存在的结构个数>8,并且当前数组的长度大于16的时候,此时将此索引位置上所有的数据改为使用红黑树存储,为了提高查找的效率

再不断的添加数据的过程中,会涉及到底层数组扩容的问题

HashMap 的底层数组在什么时候扩容,是由负载因子(Load Factor)和阈值(Threshold)决定的。负载因子是一个表示哈希表满程度的值,而阈值则是根据负载因子和数组长度计算得出的阈值。当哈希表中的元素个数超过阈值时,数组会进行扩容操作。默认扩容为原来的2倍

具体来说,扩容的触发条件是:元素个数超过阈值。而阈值的计算方式为:threshold=capacity×loadFactor
其中:
capacity 是哈希表数组的容量(数组的长度)。
loadFactor 是负载因子,默认为 0.75。
当哈希表中的元素个数达到阈值时,就会触发数组的扩容。扩容的具体过程包括:

创建新数组: 创建一个新的数组,其容量是原数组的两倍。
重新哈希: 将原数组中的所有元素重新计算哈希码,并放入新数组中。由于数组容量变化,哈希码的计算可能会得到不同的索引位置。
替换原数组: 将新数组替换为原数组。
image-20240109213415336

map的主要方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void clear():
boolean containsKey(Object key) //查询map中是否包含指定的key,如果包含则返回true
boolean containsValue(Object value)//查询map中是否包含指定的value,如果包含返回true

Object get(Object key)//返回指定可以对应的value。如果map中不包含key则返回null
boolean isEmpty() //顾名思义,空返回true
Object put(key,value);//添加一个键值对,如果已经有则覆盖原有的键值对
void putAll();//将指定map的键值复制到对应的map中去
Object remove(key);//删除指定key对应的键值对,返回相关联的value,若key不存在,则返回null
int size();//返回map中键值对的个数
int getOrDefault(key, defaultValue)//获取指定key对应对value,如果找不到key,则返回设置的默认值

//原视图操作法
Set entrySet()// 返回map中包含的所有key-value对应的set集合
Set keySet()// 返回map中所有的key组成的set集合
Collection values();//返回map里所有的value组成的Collection

HashMap中按照key和value排序的两种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*1.对key值进行排序
HashMap的存储是没有顺序的,而是按照key值的hashcode()实现的,
所以对key值排序,首先要得到HashMap中的所有key组成的集合,使用keyset()方法,并且转换为数组,这样才能用Arrays.sort()进行排序,*/
Set set = map.keyset();
Object[] arr = set.toArray();
for(Object key :arr){
map.get(key);
}
/*2.对value值进行排序
对value进行排序,首先要得到HashMap中的包含映射关系的视图entrySet
将entrySet转为List,然后重写比较器即可,
可以使用List.sort(comparator);
Collections.sort(comparator);*/

List<Map.Entry<String, Integer>> list = new ArrayList(map.entrySet()); //转换为list
//Map.Entry表示java中操作键值对的接口,Map 接口的实现类(如 HashMap)的 entrySet() 方法返回一个包含 Map.Entry 对象的集合,这样可以遍历并操作键值对。

//List<Map.Entry<String, Integer>> entryList 表示一个包含键值对的列表,其中键是 String 类型,值是 Integer 类型。这个列表被用来对键值对进行排序。

//使用list排序;
list.sort(new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o2.getValue().compareTo(o1.getValue());
}
});

//使用Collections.sort()进行排序
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o2.getValue().compareTo(o1.getValue());
}
});

image-20240105161818539

image-20240420230623402

集合和数组

长度区别:数组固定,集合长度可变

内容区别:数组可以是基本的数据类型,也可以是引用的数据类型

​ 集合只能是引用类型

元素内容:数组只能存储同一种类型

​ 集合可以存储不同类型的(一般也是同一种类型的)

collection集合的方法:

1
2
3
4
5
6
7
8
9
10
boolean add(E e);//在集合末尾添加元素
boolean remove(Object o);//删除与o值相等的元素,并且返回
void clear();//清除完集合中的所有元素
boolean contains(O o);//判断集合中是否包含元素
boolean isEmpty();//判断集合是否为空
int size();//返回集合中的元素个数
boolean addAll(Collection c);//将c中的所有元素添加到另一个集
Object[] toArray();//返回包含本集合中所有元素的数组,集合-->数组
Iterator iterator();//迭代器,集合的专用遍历方式
iterator.next(),iterator.hasnext()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
collection和collections的区别
collection是所有集合的接口,list,set,map
collections是操作集合的工具类;

collections的常用方法,都是静态方法,static,直接调用它
Collections.reverse(list); //反转集合中的元素
shuffle(list);//对集合的元素进行随机排序
sort();//升序排序
sort(list,comparator);//指定comparator的产生顺序对集合list排序
swap(list,int i,int j);//指定集合中的i,j 处的元素进行交换
max();
min();
frequency(Collection,Object o);//集合中指定元素出现的个数
copy(list dest,list,src);//将 src的内容复制到desc中
replaceAll(list,old,new);//将list中的所有旧值改为新的值
Collections提供了多个synchronizedXxx()方法,解决ArrayList,Hashmap等线程安全的问题

List集合

image-20240106164926820

总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
增:add(Object)

删:remove(int index)/remove(Object obj)

改:set(int index,Object o)

查:get(int index)

长度:size()

遍历:foreach 迭代器Iterator()

集合-->数组:toArray()
数组-->集合:Arrays.asList();

1.基本概念

java与C、C++的异同

1.相同,java和C++都是面向对象的语言,封装,继承,多态

封装:就是将对象的属性和状态封装在一个类中,并且提供公共的public来get,set此属性的值。好处是,高内聚,低耦合,隐藏对象内部的复杂性,只对外公开简单的接口,供外界调用

继承:extends,子类可以继承父类的属性和方法,不修改父类的情况下添加自己的新成员方法,子类成员通过super关键字调用父类的构造方法和成员。

多态性质:同一操作对不同的对象,有不同的解释,就是多态性(父类的引用指向子类的对象)

通过方法重写override或者方法重载

2.不同之处:

java是解释性的语言,运行过程为:java编译后生成字节码文件,然后在java虚拟机JVM中解释运行,

C++编译型语言,编译后直接生成二进制的字节码文件,所以C++运行速度快,但是java可以移植

java中没有指针,提供了数组和集合这样的类和方法去操作,使得程序更加安全

java中没法实现多重继承,只能实现多个接口来达到与C++中多重继承的作用

C++中,经常需要去malloc去分配和释放内存,java中有垃圾回收机制,会自动释放内存

C++支持运算符的重载,java不支持

C++更接近底层,允许更多的底层控制,java隐藏了更多的底层细节,提供了丰富的库和内置功能

1
2
3
4
集合collection中存储的如果是自定义的对象,需要重写哪儿些方法
list:equals()方法判断对象的属性是否相等
hashset:重写equals()和hashcode()方法,为什么要重写hashcode,这与hashset的比较过程是有关系的,存储对象会采用hash算法生成一个int类型的哈希值,如果hash值不一样判断不是相同的元素,再存储,如果hash值一样,就调用equals 方法判断,所以不去重写的话,会调用父类的hash算法,这样相同的元素就会被判断为不同的hash值,就违背了set表不能有重复元素的定义
treeset:底层二叉树,元素唯一且已经排好序,因为需要排序,所以就需要在compare方法里定义比较对象的属性,进行排序。重写compareTo(),compare()

线程安全和线程不安全

1
2
3
线程安全就是:多线程访问时候,提供一种加锁的机制,当其中一个线程访问的时候,其他的线程不能进行访问,从而达到保护数据的目的,对java中的synchronized关键字
线程不安全:不加锁,有可能出现多个线程先后更爱数据得到的是错误数据
例如:1000张票,A和B同时买票,如果线程不安全,会出现同时执行1000-1 的操作,导致最后剩下999张票,而不是998张

例题:理解hashset的底层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
        HashSet hashSet = new HashSet();
Person p1 = new Person("A", 1);
Person p2 = new Person("B", 2);
hashSet.add(p1);
hashSet.add(p2);
System.out.println(hashSet);

p1.age = 3;
hashSet.remove(p1);
System.out.println(hashSet);

hashSet.add(new Person("A",3));
System.out.println(hashSet);

hashSet.add(new Person("A",1));
System.out.println(hashSet);

//Person类重写了hashcode和equals方法

输出:
[Person{name='A', age=1}, Person{name='B', age=2}]
[Person{name='A', age=3}, Person{name='B', age=2}]
[Person{name='A', age=3}, Person{name='B', age=2}, Person{name='A', age=3}]
[Person{name='A', age=3}, Person{name='B', age=2}, Person{name='A', age=1}, Person{name='A', age=3}]

解析:先往set添加了两个元素,所以输出两个
// 修改了p1的age,移除了p1,在set中remove,还是要先计算hash值,因为修改了p1=Person("A", 3),所以哈希值变了,但是原来在底层的还是Person("A", 1),所以remove找不到,没有删除,输出仍为两个,再添加Person("A",3),重新计算hash值,可以添加成功,最后添加new Person("A",1),与之前的hash值相等,所以再调用equals判断是否为同一个对象,很明显不是同一个对象,所以也可以添加成功
//总结:尽量不要再set中修改属性,很麻烦,直接删了重新添加

栈和队列

1
2
3
4
5
6
7
8
9
Stack<Integer> ans = new Stack<>();
//常用方法
push(value)//将给定的值,压入栈的顶端,入站
pop()//删除并且返回栈顶的值,出栈
peek()//返回栈顶的值,但并不删除
isEmpty()//
size()//长度
search(Object o)//返回对象在栈中的位置

队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作,
//LinkedList实现了Queue接口,因此我们可以把LinkedList当成Queue队列来用
Queue<Integer> queue = new LinkedList<Integer>();
//add()和remove(),方法在失败的时候会抛出异常(不推荐使用)

add(E e):将元素 e 插入到队列末尾,如果插入成功,则返回 true;如果插入失败(即队列已满),则会抛出异常;

remove():移除队首元素,若移除成功,则返回 true;如果移除失败(队列为空),则会抛出异常;

remove(Object o):移除指定的元素,若移除成功,则返回 true;如果移除失败(队列为空),则会抛出异常

offer(E e):将元素 e 插入到队列末尾,如果插入成功,则返回 true;如果插入失败(即队列已满),则返回 false

poll():移除并获取队首元素,若成功,则返回队首元素;否则返回 null

peek():获取队首元素,若成功,则返回队首元素;否则返回 null

isEmpty():队列是否为空

size():队列长度

对于非阻塞队列,一般情况下建议使用 offer、poll 和 peek 三个方法,不建议使用 add 和 remove 方法。因为使用 offer、poll 和 peek 三个方法可以通过返回值判断操作成功与否,而使用 add 和 remove 方法却不能达到这样的效果。