n阶魔方阵解法优化
思想:(1-9) 将1放在魔方阵第一行的中间位置,从第二个数字开始放在1第一个数字的上一行下一列,若已经存在数据,则放在上一个数字的下一行,列数不变。
package mypratice;
import java.util.Arrays;
/**
* @Package: mypratice
* @Author:kkz
* @Description:
* @Date:Created in 2018/10/24 0:34
*/
public class Mofang {
public static void magicScqure(int[][] array,int n){
array[0][n/2] = 1; //将数字 1 放入魔方阵
int prevRow = 0;
int prevCol = n/2;
for(int i = 2;i <= n*n;i++) {
// 注意:判断上一行下一列是否有数据所用的算法,% 求余
if(array[(prevRow-1+n)%n][(prevCol+1)%n] != 0) {
//上一行的下一列有数据
prevRow = (prevRow+1)%n;//放在当前数的下一行
} else {
prevRow = (prevRow-1+n)%n; //有数据,则放在当前数的 上一行,下一列
prevCol = (prevCol+1)%n;
}
array[prevRow][prevCol] = i;
}
}
public static void main(String[] args) {
int row = 3;
int[][] array = new int[row][row];
magicScqure(array,row);
System.out.println(Arrays.deepToString(array));
}
}
[[8, 1, 6], [3, 5, 7], [4, 9, 2]]
字符串操作
java字符串是Unicode字符的有序集合,Unicode字符是使用UTF-16进行编码的。在Java语言中,字符串作为对象,这与在C语言作为字符数组来处理不同。
java语言使用java.lang包中的String、StringBuilder 和 StringBuffer来构造自定义字符串,执行许多基础字符串操作,如比较字符串、搜索字符串、提取子字符串等。
注:String、StringBuilder 和 StringBuffer均为密封类,不能派生子类。(密封类:类和方法 被关键字 final 修饰之后,类不能被继承,方法不能被修改。密封类的优点:密封类可以防止有意的派生。)
String类
String 对象是不可变的(只读),因为一旦创建了该对象,就不能修改对象的值。有些字符串操作看来似乎修改了String对象,实际上返回了一个包含修改内容的新String对象。如果需要修改字符串对象的实际内容,可以使用StringBuilder 或 StringBuffer 类。String类源代码:
//final 所修饰的类不能改变其值, String类是不可变类
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage.
private final char value[]; //value[] 数组用于存储字符串
- 例:1. 声明和初始化字符串,并判断输出结果是否相等:
public class Testdome1024 {
public static void main(String[] args) {
String str1 = "hello"; //str1 变量(变量在运行时才可知其值) hello 字符串常量(常量在编译时统一处理)
String str2 = new String("hello"); //new 实例化 产生对象
String str3 = "he" +"llo"; //常量
String str4 = new String("he") + new String("llo");
System.out.println(str1 == str2);
System.out.println(str3 == str1);
System.out.println(str3 == str2);
System.out.println(str4 == str3);
}
}
false
true
false
false
- 内存分配图如下:
谈谈栈与堆的存储:
1.堆(对象):
引用类型的变量 ,其内存分配在堆上或者常量池(字符串常量,基本数据类型常量),需要通过new等方式创建。
堆内存的主要作用是存放运行时new创建的对象。(主要用于存放对象,存取速度慢,可以运行时动态分配内存,生存期不需要提前确定)。
2.栈(基本数据类型变量,对象的引用变量):
基本数据类型的变量(int、short、long、byte、float、double、boolean、char等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放。
栈内存的主要作用是存放基本数据类型和引用变量。栈的内存管理是通过栈的"后进先出"模式来实现的。(主要用来执行程序,存取速度快,大小和生存期必须确定,缺乏灵活性)
- 2.运用“+”连接字符串,判断是否相等:
public class Stringa {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "world";
String str3 = "helloworld";
System.out.println(str1 + str2);
System.out.println(str3);
System.out.println(str3 == (str1 + str2));
System.out.println(str3 == ("hello"+"world"));
}
}
helloworld
helloworld
false
true
- 内存分配图如下:
运用jdk自带的工具javap来反编译以上代码,命令为:
javac src\java文件存储位置 javap -c src.java文件存储位置
显示字节码如下:
G:\javacode\HelloWorld>javac src\mypratice\Stringa.java
G:\javacode\HelloWorld>javap -c src.mypratice.Stringa
警告: 二进制文件src.mypratice.Stringa包含mypratice.Stringa
Compiled from "Stringa.java"
public class mypratice.Stringa {
public mypratice.Stringa();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String world
5: astore_2
6: ldc #4 // String helloworld
8: astore_3
9: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
12: new #6 // class java/lang/StringBuilder
15: dup
16: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
19: aload_1
20: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: aload_2
24: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
30: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_3
37: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
43: aload_3
44: new #6 // class java/lang/StringBuilder
47: dup
48: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
51: aload_1
52: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
55: aload_2
56: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
59: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
62: if_acmpne 69
65: iconst_1
66: goto 70
69: iconst_0
70: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
73: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
76: aload_3
77: ldc #4 // String helloworld
79: if_acmpne 86
82: iconst_1
83: goto 87
86: iconst_0
87: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
90: return
}
从生成的字节码文件中可以看出,"+"操作符在代码中的实现过程实际上是编译器自动引用了java.lang.StringBuilder()类,为每一个字符串调用了StringBuilder的append()方法,接下来会具体写到String类的常用方法。
-
- 判断字符串是否相等,第三种情况。
public class Stringa {
public static void main(String[] args) {
String str1 = new String("hello");
String str2 = "hello";
System.out.println(str1 == str2);//false
String str3 = "he"+new String("llo");
System.out.println(str1 == str3);//false
System.out.println(str2 == str3);//false
String str4 = "he"+"llo";
System.out.println(str4 == str2);//true
char[] array = {'h','e','l','l','o'};
String str5 = new String(array);
System.out.println("=============");
System.out.println(str1 == str5);//false
System.out.println(str2 == str5);//false
System.out.println(str3 == str5);//false
System.out.println(str4 == str5);//false
}
}
- 内存分配图如下:
String类的常用方法
下面实践常用的几个方法:
- 字符串空判断:String.isEmpty()
- 源代码:
public boolean isEmpty() {
return value.length == 0;
}
- 例:
public class Stringa {
public static void main(String[] args) {
String str1 ="world";
str1.length();
str1.isEmpty();
System.out.println(str1.length()); //返回字符串长度
System.out.println(str1.isEmpty());//判断字符串是否为空,如果字符串的length()为0,则返回true
}
5
false
- 获取字符、截取字符串:
(1)返回指定索引处的字符串:String.ChatAt()
- 源代码:
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
(2)截取子字符串:(从begindex到结束):String.substring()
- 源代码:
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}//开始索引小于0,抛出异常
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
(3)截取部分子字符串(从begindex到endindex):String.substring()
- 源代码:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}//开始索引小于0,抛出异常
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}//结束索引大于字符串长度,抛出异常
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
- 例:
public class Stringa {
public static void main(String[] args) {
String str1 ="world";
System.out.println(str1.charAt(4));
System.out.println(str1.substring(0));//截取整个字符串
System.out.println(str1.substring(0,3));//截取部分字符串
}
}
d
world
wor
- (1)拷贝整个char数组给目标数组:String.getChars()
- 源代码:
//拷贝整个数组给dst 数组
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { //起始索引,结束索引,目标数组,目标数组起始索引
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); //源数组,起始索引,目的数组,目的数组起始索引,源数组拷贝长度
} //底层调用的是System。arraycopy()方法
(2)复制byte到目标数组:String.getBytes()
@Deprecated 注解
//已经过时的 方法 调用时会有横线划掉
public void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
Objects.requireNonNull(dst);
int j = dstBegin;
int n = srcEnd;
int i = srcBegin;
char[] val = value; /* avoid getfield opcode */
while (i < n) {
dst[j++] = (byte)val[i++];
}
}
- 例:
public class Stringa {
public static void main(String[] args) {
String str1 ="myprogrammingworld";
char[] a2 = new char[15];
str1.getChars(0,12,a2,0);
System.out.println(a2); //拷贝char到目标数组
byte[] a3 = new byte[10];
str1.getBytes(0,10,a3,0); //已经过时的方法,使用时会有划线提示
System.out.println(a3);//拷贝byte到目标数组
}
}
myprogrammin
[B@154617c
- 比较字符串:String.equals()
String.compareTo()
注:equals、compareTo方法用于比较字符串内容,“==”运算符用于比较对象。
- 源代码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
//anObject是否为String的实例
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
- 为每个唯一字符序列生成一个且仅生成一个String引用:String.intern()
- 源代码:
public native String intern();//intern():如果在常量池当中没有字符串的引用,那么就会生成一个在常量池当中的引用;相反:则不生成
- 例:
public class Testdome1024 {
public static void main1(String[] args) {
String str1 = new String("ab")+new String("cdef");
//str1.intern(); //true
String str2 = "abcdef";
str1.intern();
System.out.println(str1 == str2);//false
}
public static void main2(String[] args) {
String string = new String("abcdef");
string.intern();
String string2 = "abcdef";
System.out.println(string == string2);//false
}
public static void main(String[] args) {
String string = new String("abcdef").intern();
String string2 = "abcdef";
System.out.println(string == string2);//true
}
}
- 理解:
String str1 = new String("ab")+new String("cdef");
str1.intern();
String str2 = "abcdef";
System.out.println(str1 == str2);//true
在JDK 1.7下,当执行str1.intern();时,因为常量池中没有“abcdef”这个字符串,所以会在常量池中生成一个对堆中的“abcdef”的引用(注意这里是引用 ,就是这个区别于JDK 1.6的地方。在JDK1.6下是生成原字符串的拷贝),而在进行String str2 = “abcdef”;字面量赋值的时候,常量池中已经存在一个引用,所以直接返回了该引用,因此str1和str2都指向堆中的同一个字符串,返回true。
String str1 = new String("ab")+new String("cdef");
String str2 = "abcdef";
str1.intern();
System.out.println(str1 == str2);//false
将中间两行调换位置以后,因为在进行字面量赋值(String str2 = “abcdef″)的时候,常量池中不存在,所以str2指向的常量池中的位置,而str1指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,所以返回false。
- 内存分配:
- String replace(CharSequence oldString,CharSequence newString):
返回一个新字符串。这个字符串用newString代替原始字符串中的所有oldString。可以用String或StringBuilder对象作为CharSequence参数。 - boolean startsWith(String predix):
如果字符串以preffix 字符串开始,返回true。 - String toLowerCase()::返回一个新的字符串,这个字符串将原始字符串中的所有大写字母改写成了小写字母。
- String toUpperCase():返回一个新的字符串,这个字符串将原始字符串中的小写字母改成了大写字母。
- String trim(): 返回一个新的字符串,这个字符串将删除了原始字符串头部和尾部的空格。
StringBuilder 和 StringBuffer
字符串(String)对象是不可变的,即它们在创建之后就无法更改。对字符串进行操作,要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。
String s1 = “Hello”;
String s2 = s1;
s1 += “and goodbye”; //创建新的字符串对象
System.out.println(s2);
如果需要对字符串执行重复修改,则与创建新的String对象相关的系统开销可能会非常昂贵。如果要修改字符串而不创建新的对象,则可以使用StringBuilder或StringBuffer类。
StringBuilder 类和StringBuffer类都表示值为可变字符序列的类似字符串的对象,可通过追加、移除、替换或插入字符创建它后对它进行修改。二者的API完全兼容。
StringBuffer类是线程安全类,可安全地用于多个线程;StringBuilder类则是StringBuffer的一个简易替换,用在字符串缓冲区被单线程使用的时候。相比StringBuffer,StringBuilder具有更高的性能。
StringBuffer
- 多线程使用
- StringBuffer.append ("") 追加 append返回当前的对象
- synchronized 关键字 保护线程 线程安全
- 单线程 多线程 线程池
- StringBuilder String 单线程使用
** 练习 **
1.左旋数组:“abcdef”==>“cdefab” 参数 String 位置 n
编写函数,将字符串从左数第n个开始进行旋转(也叫左旋数组)。 - 思路: 例如:String str = “abcdef”;从第二个开始旋转,旋转后的结果为:”cdefab”;可以先将左旋前面的部分字符串逆置得到:“bacdef”; 再将后面部分字符串逆置得到:”bafedc”;最后整体再逆置一次得到:”cdefab”;就完成了。
public class LeftArray {
public static String leftarray(String str,int n){
if(str == null || n > str.length()) {
return null;
}
int left1 = 0;
int right1 = n-1;
int left2 = n;
int right2 = str.length()-1;
str = reverse(str,left1,right1);//左旋前面的部分字符串逆置 得到ba cdef
str = reverse(str,left2,right2);//后面部分字符串逆置 得到 ba fedc
str = reverse(str,left1,right2);//整体再逆置 得到 cdefab
return str;
}
private static String reverse(String str, int start, int end) {
char ch[] = str.toCharArray();//将字符串转成字符数组
char tmp;
while(start < end) {
tmp = ch[start];
ch[start] = ch[end];
ch[end] = tmp;
start++;
end--;
}
return String.copyValueOf(ch);//将字符数组转成字符串
}
public static void main(String[] args) {
String str = "abcdef";
System.out.println("原字符串为:"+str);
Scanner sc = new Scanner(System.in);
System.out.println("请输入从第几个元素开始左旋");
int n = sc.nextInt();
System.out.println("左旋后字符串为:"+ leftarray(str,n));
}
}
原字符串为:abcdef
请输入从第几个元素开始左旋
2
左旋后字符串为:cdefab