目录
写在前面:本人目前为软件工程大二的学生,大学期间摆烂了一年多,到这学期才开始悔悟,现在从最基础的开始学起,该文章也是想唤醒那些和我一样的同学们,也适用于Java初学者来学习该内容。文章内容可能是很简单的,语法上也很稚嫩,但是我会持续更新内容的。如果文章里面有错误的地方,也希望能和大佬们批评指正。
!!!最后祝福大家都能在自己未来的道路上一路生花!!!
一、Java字符串
1、字符串比较
String str="Hello World!";
String anotherStr="hello world!";
Object obj=str;
System.out.println(str.compareTo(anotherStr));//32
System.out.println(str.compareToIgnoreCase(anotherStr));//0
System.out.println(str.compareTo(obj.toString()));//0
该处定义了三个对象,两个字符串对象以及一个Object对象,通过compareTo()和compareToIgnoreCase()两个函数比较了这三个字符串;
compareTo()函数的比较逻辑是比较两个字符串的相同索引是否相同,如果遇到不同的话就会返回两个字符的ASCII码的差值,并且函数会停在该处;如果相同的话返回0;
compareToIgnoreCase()这个函数是忽略掉了大小写的问题
2、查找字符串第一次和最后一次出现的位置
String str1="https://www.csdn.net.csdn.net";
String str2="csdn";
String str3="net";
System.out.println(str1.indexOf("n"));//第一个字符n出现的位置 ->15
System.out.println(str1.indexOf(str2));//第一个字符串str2出现的位置 ->12
System.out.println(str1.lastIndexOf(str3));//最后一个字符串str3出现的位置 ->26
System.out.println(str1.indexOf(str2,13));//从第13个字符开始,第一个字符串str2出现的位置 ->21
indexOf()是用来查找某个字符或字符串在某个字符串中第一次出现的位置,这里查找的是第一个n在str里面出现的位置(应和数组索引的表示一样)
lastIndexOf()用来查找某个字符或字符串在某个字符串中最后一次出现的位置,这里我查找了net在str1中出现的最后一次位置,应该是最后一个net,即索引26;
3、删除字符串中的一个字符
我们要删除字符串中的一个字符,可以根据索引删除以达到我们所想要删除的那个字符,也可以根据查找字符串中这个字符出现的第一次或者最后出现的一次位置再来删除(如果知道需要删除的那个字符为临界值的时候)
//根据索引删除字符
public static String removeCharAt(String str,int index){
return str.substring(0,index)+str.substring(index+1);
}
//根据字符删除字符(一般为临界值的时候)
public static String removeChar(String str,char c){
return removeCharAt(str,str.indexOf(c));
}
}
4、字符串的替换
在日常编写代码中,我们会遇到一些要求我们对字符串内容进行替换的题目,下面我就简单演示一个替换字符的操作
String str="Hello World!";
System.out.println(str.replace('H', 'A'));//将str中所有的H替换为A
System.out.println(str.replace("Hello", "Hi"));
其中需要注意的是,利用字符串中的方法replace()的时候,该方法是将字符串中所有的被替换字符替换为目标字符,要想替换具体的某个字符的话可以先获取到该字符的索引再替换,如果是第一个字符的话也是使用replaceFirst()方法替换
5、字符串的反转
实现字符串的反转,利用到StringBuilder类,因为String类是不可变的,利用StringBuilder是一个可变容器来实现对字符串的反转,逆向遍历字符串,获取到每个字符,从str.length()-1开始到i=0,此时的sb对象就是一个反转后的str,再return sb.toString();返回字符串对象
public static String reverseStr(String str) {
StringBuilder sb = new StringBuilder();
for (int i = str.length() - 1; i >= 0; i--) {
sb.append(str.charAt(i));
}
return sb.toString();//返回字符串对象
}
6、是否包含子串
要判断一个字符串中是否含有某个子串,首先拿到子串的首字符,再去遍历父串,碰到与首字符firstChar相同的字符的时候进入if语句,再判断往后的字符是否相同
public static boolean contained(String str,String str2){
char firstChar = str2.charAt(0);//子串的首字符
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == firstChar) {
int j = 0;
while (j < str2.length() && i + j < str.length() && str.charAt(i + j) == str2.charAt(j)) {
j++;
}
if (j == str2.length()) {
return true;
}
}
}
return false;
}
7、修改字符串的内容
当我们定义了一个字符串的时候,假设original="hello"
JVM会在串池中检查是否存在值为hello的对象,没有的话就创建一个,然后将该对象的引用赋值给original,此时的original是指向hello的引用,并且根据hello值计算出哈希码;
当调用strModify(original)的时候,,此时方法传递的是original引用的副本,而不是对象本身,str和original同时指向串池中的hello对象;
当方法中更改str的值的时候,JVM同样会去串池中检查是否有值为"this str has been changed"的对象,没有的话创建一个新的对象,并且把str指向这个引用,因此此时的str的哈希码是"this str has been changed"对象的哈希码
// 修改字符串内容
public static String strModify(String str){
System.out.println("旧的str的值是:"+str);
System.out.println("旧的str的hashcode是:"+str.hashCode());
str="this str has been changed!";
System.out.println("新的str的值是:"+str);
System.out.println("新的str的hashcode是:"+str.hashCode());
return str;
}
运行结果:
8、在字符串的指定位置插入另一个字符串
想要在一个字符串中插入另一个字符串的话,可以根据需要插入的索引来插入。
首先定义一个字符串容器StringBuilder用于存放结果字符串,先把索引前面的字符串拼接到newstr上,然后再拼接需要插入的字符串,最后把原字符串剩余的字符串拼接上去
public static void strBufferInsert(StringBuffer a,StringBuffer b, int n){
StringBuilder newstr=new StringBuilder();
int i=0;
int j=n;
while(i<a.length()){
if(i<n){
newstr.append(a.charAt(i));
i++;
}else if(i==n){
newstr.append(b.toString());
i++;
}else{
newstr.append(a.charAt(j));
j++;
i++;
}
}
System.out.println(newstr.toString());
}
9、压缩字符串
如果遇到需要压缩字符串的题目的时候,如:将给定的字符串,连续重复出现的字母用数字表示重复率,输出压缩后的字符串
定义一个字符,用来存储目前处理的字符,遍历原字符串,遇到与currentChar相同的字符的时候count+1,否则就为单一字符,不需要压缩处理,直接加到字符串容器里面。如果count是大于1的话就是需要压缩的字符,把count加到字符串容器里,最后更新currentChar为最后处理到的索引位置,count置1。
当程序处理完倒数第二个字符后,依旧会处理count部分的代码,但是不会把最后一个字符及count添加到容器后面,所以需要对最后一个字符进行特殊处理(你们有好的代码也可以向我分享)
public static String strCompress(String str) {
if (str == null || str.isEmpty()) {
return str;
}
StringBuilder compressed = new StringBuilder();
char currentChar = str.charAt(0);
int count = 1;
for(int i=1;i<str.length();i++){
if(str.charAt(i)==currentChar){
count++;
}
else{
compressed.append(currentChar);
if(count>1){
compressed.append(count);
}
currentChar = str.charAt(i);
count=1;
}
}
// 处理最后一个字符
compressed.append(currentChar);
if(count>1){
compressed.append(count);
}
// 如果压缩后的字符串比原字符串短,则返回压缩后的字符串,否则返回原字符串
return compressed.length() < str.length() ? compressed.toString() : str;
}
10、去掉语句里的空格
在遇到需要消除语句里面的空格的时候,首先可以先获取到该语句里面所含的词汇个数(用于定义数组大小),将空格前面的词汇分别填充到字符串数组里,然后遍历打印数组。
当然小编的这种方法是很愚笨的,这里鼓励大家尝试更简单的方法,也可以相互交流
public static String[] strSplit(String str){
int n=1;
for (int i=0;i<str.length();i++){
if (str.charAt(i)==' '){
n++;
}
}
String[] strArray=new String[n];
StringBuilder stringBuilder =new StringBuilder();
int count=0;
for (int i=0;i<str.length();i++){
char c=str.charAt(i);
if (c !=' '){
stringBuilder.append(c);
}else{
strArray[count]= stringBuilder.toString();
stringBuilder = new StringBuilder();
count++;
}
}
strArray[count]=stringBuilder.toString();
return strArray;
}
11、字符串的性能比较
我在网上看到一个有意思的问题:创建字符串有两种方式
第一种是直接创建 String str = "hello";
第二种是用对象创建 String str = new String("hello");
那么问题来了,这两种字符串的创建方式,在性能上有什么区别吗?
long startTime = System.currentTimeMillis();
for(int i=0;i<50000;i++){
String s1 = "hello";
String s2 = "hello";
}
long endTime = System.currentTimeMillis();
System.out.println("通过 String 关键词创建字符串"
+ " : "+ (endTime - startTime)
+ " 毫秒" ); //6ms
long startTime1 = System.currentTimeMillis();
for(int i=0;i<50000;i++){
String s3 = new String("hello");
String s4 = new String("hello");
}
long endTime1 = System.currentTimeMillis();
System.out.println("通过 String 对象创建字符串"
+ " : " + (endTime1 - startTime1)
+ " 毫秒"); //14ms
在结果上来看,运行5000次循环,直接用关键字创建的字符串花费了6ms,而用对象的方式却是14ms,虽然这几毫秒在我们看来是微乎其微的,但是,造成这个现象的是为什么呢?
你知道的,通过关键字创建字符串对象的时候,JVM会到串池里面去看是否存在一个"hello"对象,如果有的话就直接将他的引用赋值给s1,s2。在整个循环里面,s1和s2一直都是指向串池里面的"hello"对象的,并没有创建新的字符串。而第二种方式,每次都会强制性在堆中创建出一个String类型的对象,然后再将创建出来的对象指向串池里面的"hello"对象(虽然参数“hello”本身就会复用串池中的“hello”对象)。
还有就是关于JVM的一个优化问题,当使用关键字创建字符串对象,该种方法没有副作用以及这两个对象均为被后续代码所使用,JVM可能会将整个循环优化成一个空操作(甚至完全移除)。
而使用new创建对象会触发对象分配,JVM是无法确定其副作用的(即使实际上没有),所以无法完全优化掉循环,每次的迭代创建对象都要执行内存分配,这导致了耗时大大增加。
二、Java数组
1、数组排序
数组里面的排序其实就是从左到右遍历一遍数组,顺便把数组里面的元素按照一定的排列顺序将数组有序化。这里我以冒泡排序为例来演示。
public static void main(String[] args) {
int[] array = {6,-2,3,9,0,-1,5,-7,2,1};
sort(array);
printAll(array);
}
//把排序逻辑封装起来
static void sort(int[] array){
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length-i-1; j++){
if (array[j]>array[j+1]){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
}
//把遍历数组的逻辑封装起来
static void printAll(int[] array){
for (int i = 0; i < array.length; i++) {
if(i==0){
System.out.print("[" + array[i] + ",");
}
else if(i>0 && i<array.length-1){
System.out.print(array[i] + ",");
}
else{
System.out.println(array[i] + "]");
}
}
}
这是按照冒泡排序,从小到大依次排列的,大家也可以按照自己的需求实现不同的逻辑。
但是需要提到的是,Java其实已经为我们提供了一个工具类Arrays,里面提供了sort()方法,我们直接调用这个工具类里面的sort()函数即可实现数组排序,但是还是希望大家能自己手搓一遍底层代码。
2、数组元素查找
想必每个计算机学生都离不开元素查找这个题目,这里我也为大家演示一下,如何在数组里面实现固定元素的查找并且返回该元素所在数组里面的索引。
public static void main(String[] args) {
int[] array = {6,-2,3,9,0,-1,5,-7,2,1};
int target = 10;
int index = search(target,array);
System.out.println(index != -1? target + "在数组中的第"+index+"个位置" : target + "在数组中未找到");
}
static int search(int target,int[] array){
for (int i = 0; i < array.length; i++) {
if (array[i]==target){
return i;
}
}
return -1;
}
在这里我只是演示了一个非常简单的元素查找的逻辑,并没有考虑性能的方面,大家可以多去了解算法,能更加节省查找的时间(在数据规模足够大的时候)。
同样的,这里也存在工具类Arrays提供的函数,binarysearch()进行二分查找,感兴趣的同学可以去了解一下二分查找,为以后的数据结构打下基础。
3、数组添加元素
想要在数组中添加元素,首先我们需要考虑到的是,数组在创建的时候是已经被固定了大小。如果说,当前的数组大小刚好是装满的时候,我们此时再往数组里面添加元素的话,肯定是会报越界异常的,那么此时我们就需要另一个更大的数组来替换掉原来的数组,如何实现呢?
public static void main(String[] args) {
int[] array = {6,-2,3,9,0,-1,5,-7,2,1};
int[] newArray = insertElement(array,4,3);
for (int i = 0; i < newArray.length; i++) {
System.out.println(newArray[i]);
}
}
static int[] insertElement(int[] array,int element,int index){
int[] newArray = new int[array.length+1];
for(int i=0;i<index;i++){
newArray[i] = array[i];
}
newArray[index] = element;
for (int i = index+1 ;i<newArray.length;i++){
newArray[i] = array[i-1];
}
return newArray;
}
这里我用了一种很简单易懂的方式来插入指定索引的元素,考虑到整型数组中未定义的元素初始值为0,而我们无法判别该 "0"是否为合法 "0",所以我们在插入值的时候直接定义一个比原始数组长度大一的新数组,把需要插入的位置之前的元素拷贝到新数组里面,再把待插入值插入需要插入的索引位置,最后再把原始数组的index位置及其后面的元素拷贝到新数组的index+1的位置即可。
这里有Java为我们提供的已经封装好的方法arraycopy(原始数组,需要拷贝的原始数组起始索引,目标数组,需要拷贝到的目标数组的起始索引,拷贝长度)
在这里也就是:
//将index前的数据拷贝到新数组
System.arraycopy(array,0,newArray,0,index);
//插入待插入值
newArray[index] = element;
//将index及其以后的数据拷贝到新数组
System.arraycopy(array,index,newArray,index+1,array.length()-index)
4、数组的反转
想要实现一个数组里面的内容反转,其实最本质的道理就是实现数组里面对称位置的内容互换,无关数组长度的奇偶性。可以利用双指针的思想来完成该题目,头指针和尾指针相向运动,最后满足头指针大于尾指针的时候,数组即满足了反转。
public static void main(String[] args) {
int[] array = {6,-2,3,9,0,-1,5,-7,2,1};
int[] newArray = reverse(array);
for (int i = 0; i < newArray.length; i++) {
System.out.print(newArray[i]+" , ");
}
}
static int[] reverse(int[] array){
for (int i = 0,j=array.length-1; i <=j; i++,j--) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
5、获取数组的最值
想要获取到数组的最值,可以首先把数组里面零索引的位置的元素看成最值,再遍历数组,如果遇到比该最值大(或小)的值的时候,更新这个最值,直到遍历完数组。
这里我仅以最大值为例,最小值也是同样的道理
public static void main(String[] args) {
int[] array = {6,-2,3,9,0,-1,5,-7,2,1};
System.out.println("数组的最大值为:" + findmax(array));
}
static int findmax(int[] array){
int max=array[0];
for (int i = 1; i < array.length; i++) {
if (array[i]>max){
max = array[i];
}
}
return max;
}
6、数组合并
(1)带排序的合并
假定有两个已经有序的数组,需要将这两个数组进行合并,并且要求合并后的数组依然是有序的,那么我们可以按照以下逻辑完成。
public static void main(String[] args) {
int[] array1 = {1,3,5,7,9};
int[] array2 = {2,4,6,8,10};
int[] merge = mergeandsort(array1,array2);
for (int i = 0; i < merge.length; i++) {
System.out.print(merge[i] + " , ");
}
}
static int[] mergeandsort(int[] array1,int[] array2){
int[] megerArray = new int[array1.length+array2.length];
int i = 0, j = 0, k = 0;
while(i<array1.length && j<array2.length){
if(array1[i]<array2[j]){
megerArray[k++] = array1[i++];
}else{
megerArray[k++] = array2[j++];
}
}
while (i<array1.length){
megerArray[k++] = array1[i++];
}
while (j<array2.length){
megerArray[k++] = array2[j++];
}
return megerArray;
}
当然,我们同样可以使用先合并,在排序的思想,之前我不是介绍过有一种合并数组的方法吗?我们可以利用这个方法直接将两个数组先合并,再进行排序,例如:
public static void main(String[] args) {
int[] array1 = {1,3,5,7,9};
int[] array2 = {2,4,6,8,10};
int[] mergeArray = new int[array1.length + array2.length];
System.arraycopy(array1,0,mergeArray,0,array1.length);
System.arraycopy(array2,0,mergeArray,array1.length,array2.length);
Arrays.sort(mergeArray);
for (int i = 0; i < mergeArray.length; i++) {
System.out.print(mergeArray[i] + " , ");
}
}
在这个实现里面,我就是用到了之前提到的arraycopy这个方法,以及Arrays提供的sort方法完成了这个题目。
(2)不带排序的合并
如果想要不带排序的合并的话,那么相信大家都能完成这个练习,因为(1)里面已经提供了方法如何直接合并两个数组,只需要去掉调用sort方法即可。这里我便不再做演示了。
7、数组的填充
想要实现对一个数组进行填充,我们有两种填充方法,一种是全部填充,另一种是按照指定索引以及结束索引进行填充。
(1)全部填充
如果想要全部填充的话,可以直接进行赋值操作,例如:
public static void main(String[] args) {
int[] array = new int[10];
fillAll(array, 5);
printAll(array);
}
static void fillAll(int[] array, int value){
for(int i = 0; i < array.length; i++){
array[i] = value;
}
}
static void printAll(int[] array){
for(int i = 0; i < array.length; i++){
if(i==0){
System.out.print("[" + array[i] + ",");
}
else if(i>0 && i<array.length-1){
System.out.print(array[i] + ",");
}
else{
System.out.println(array[i] + "]");
}
}
}
(2)按照指定起始索引填充
要想按照指定起始索引填充的话,那么肯定是需要起始位置的,得到起始位置以及结束位置,那么就可以完成填充了,例如:
public static void main(String[] args) {
int[] array = new int[10];
fillAtIndex(array, 5, 3, 7);
printAll(array);
}
static void fillAtIndex(int[] array, int value,int startIndex,int endIndex){
if(startIndex>array.length-1 || startIndex<0){
System.out.println("startIndex out of range");
return;
}
if (endIndex<0){
System.out.println("endIndex out of range");
return;
}
//结束索引超出数组长度时默认为最后一个元素
if(endIndex>array.length-1){
endIndex = array.length-1;
}
for (int i=startIndex;i<=endIndex;i++){
array[i] = value;
}
}
static void printAll(int[] array){
for(int i = 0; i < array.length; i++){
if(i==0){
System.out.print("[" + array[i] + ",");
}
else if(i>0 && i<array.length-1){
System.out.print(array[i] + ",");
}
else{
System.out.println(array[i] + "]");
}
}
}
这里需要考虑到的是起始索引和结束索引的边界问题,但是这里我只是处理了结束索引超出数组长度时候的情况,这时默认把结束索引更新为最后一个元素。
8、数组的扩容
实现数组扩容的思想其实也就是定义一个比原始数组大的新数组,然后将原始数组的内容原封不动的拷贝到新数组上。
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] newArray = arrayextends(array,2);
ArrayUtils.printAll(newArray);//这里的ArrayUtils是我自定义的一个工具类,里面定义了一个printAll()方法,不然每次都要重写这个方法很麻烦
}
static int[] arrayextends(int[] array,int size){
int[] newArray = new int[array.length+size];
for (int i = 0; i <array.length; i++) {
newArray[i] = array[i];
}
return newArray;
}
9、查找数组中的重复元素
由于猪波想要用动态规划数组手搓出这个题目,但是好像有点麻烦,所以我还是用集合吧,嘻嘻。
public static void main(String[] args) {
int[] array = {1,1,2,3,4,5,5,6,7,8,8};
findDupicate(array);
ArrayUtils.printAll(findDupicate(array));
}
static int[] findDupicate(int[] array){
List<Integer> list =new ArrayList<>();//用来存放重复的元素
List<Integer> seenlist =new ArrayList<>();//用来存放已经遍历过的元素
for(int num:array){
if(seenlist.contains(num)){
if (!list.contains(num)){
list.add(num);
}
}
else{
seenlist.add(num);
}
}
int[] duplicateArray = new int[list.size()];
for (int i=0;i<list.size();i++){
duplicateArray[i] = list.get(i);
}
return duplicateArray;
}
思路是这样的,我准备了两个集合list和seenlist,list用来存放重复的元素,seenlist用来判断已经判断过的元素值,首先遍历了原始的数组,如果说,seenlist里面不存在该元素的话,就放到seenlist里面,当再次遇到该元素值的时候,说明这是重复的元素,如果list里面没有该元素值,则把该元素值添加进list里面,最后把ArrayList转化成Array数组返回。
10、删除数组里面的元素
(1)按照值删除
其中,按照值删除元素的逻辑其实就是把从数组里面查找到待删除值的索引后(index+1)的内容依次覆盖索引(index)的内容,index++,最后再处理最后一个元素的值(通常可以是置零)。
需要提到的是,如果数组里面存在相同值,则用户需要判断是否需要全部删除,还是只删除遇到的第一个待删除值,这里只是添加一个break语句即可。
public static void main(String[] args) {
int[] array = {3,7,5,2,9,6,8,1,5};
deleteAtValue(array, 5);
ArrayUtils.printAll(array);
}
static void deleteAtValue(int[] array,int value){
for(int i=0;i<array.length;i++){
if(array[i] == value){
if (i != array.length-1){
for (int j=i;j<array.length-1;j++){
array[j] = array[j+1];
}
//处理最后一个元素,置零
array[array.length-1] = 0;
}else
array[i] = 0;
break;//如果只需要删除遇见的第一个元素,则break跳出循环
}
}
}
(2)按照索引删除元素
按照索引删除元素的逻辑和按照值删除元素的逻辑差不多,都是需要走用后一个元素覆盖前一个元素的操作,最后再把最后一个元素置零。这里有一个优化的方法就是,先判断待删除索引是否为最后索引,如果是的话,不用执行循环,直接把最后一个元素置零。
public static void main(String[] args) {
int[] array = {3,7,5,2,9,6,8,1};
deleteAtIndex(array, 3);
ArrayUtils.printAll(array);
}
static void deleteAtIndex(int[] array,int index){
if (index < array.length){
for (int i=index;i<array.length-1;i++){
array[i] = array[i+1];
}
}
array[array.length-1] = 0;
}
11、查找数组里面的交集
想要查找数组里面的交集,也就是拿一个数组里面的元素去依次遍历另一个数组,查看是否另一个数组里面存在相同的元素,如果存在的话就把该元素放到新的集合里面,最后再返回新的集合。
public static void main(String[] args) {
int[] array1 ={1,2,3,4,5,6};
int[] array2 ={2,3,4};
ArrayUtils.printAll(retain(array1,array2));
}
static int[] retain(int[] array1,int[] array2){
int[] retainArray = new int[Math.max(array1.length,array2.length)];
int k=0;
for (int i=0;i<array1.length;i++){
if(ArrayUtils.findValue(array2,array1[i])){
retainArray[k++] = array1[i];
}
}
return retainArray;
}