1、System.arraycopy方法实现数组的复制

1-1:System中提供了一个native静态方法arraycopy(),可以使用这个方法实现数组之间的复制。对于普通的一维数组来说,会复制每个数组的值到另一个数组中,即每个元素都是按值传递,修改副本不会影响原来的值。方法原型及复制复制基本类型数组的示例如下:

/**
 * System.arraycopy的方法原型
 * @param src       要复制的源数组
 * @param srcPos    源数组要复制的起始位置(从0开始)
 * @param dest      要复制的目标数组
 * @param destPos   目标数组的起始位置(从0开始)
 * @param length    要复制的长度
*/
public static native void arraycopy(Object src,  int  srcPos,
            Object dest, int destPos, int length);
public static void main(String[] args) {
    int[] nums = {1024, 1025, 1026, 1027, 1028};
    int[] copyOfNums = new int[nums.length];
    System.arraycopy(nums, 0, copyOfNums, 0, nums.length);
    //修改拷贝数组的元素
    copyOfNums[3] = 1234;
    //观察原数组有无变化
    System.out.println(Arrays.toString(nums));
}

输出结果: [1024, 1025, 1026, 1027, 1028]
因为复制普通数组时,按值传递,会把每个元素的值复制一份给新数组,所以修改副本不会影响原来的值。
blob.jpg

字符串数组就比较特殊了,先看代码:

String[] names = {"王昭君", "赵飞燕", "陈圆圆", "杨玉环", "苏妲己"};
System.out.println("原数组中每个元素的哈希码:");
for(String name : names) {
    System.out.print(Integer.toHexString(name.hashCode()) + ", ");
}
String[] copyOfNames = new String[names.length];
System.arraycopy(names, 0, copyOfNames, 0, names.length);
copyOfNames[1] = "洛神甄氏";
System.out.println("\n复制数组中每个元素的哈希码,1号元素的哈希码已经发生了改变:");
for(String name : copyOfNames) {
    System.out.print(Integer.toHexString(name.hashCode()) + ", ");
}
System.out.println();
//修改copyOfNames中元素后的原数组内容没有改变
System.out.println(Arrays.toString(names));

运行后的结果如下:
原数组中每个元素的哈希码:
1be7059, 225f8ec, 23f0508, 1929eae, 1f6458e,
复制数组中每个元素的哈希码,1号元素的哈希码已经发生了改变:
1be7059, 336eea6e, 23f0508, 1929eae, 1f6458e,
[王昭君, 赵飞燕, 陈圆圆, 杨玉环, 苏妲己]
内存图如下:
blob.jpg

1-2:复制对象数组

//实体类
public class Beauty {
    private String name;
    private int level;
    private double face;

    public Beauty() {}

    public Beauty(String name, int level, double face) {
        this.setName(name);
        this.setLevel(level);
        this.setFace(face);
    }

    @Override
    public String toString() {
        return name + ", " + level + ", " + face;
    }
    //省略 getters/setters
}
//测试方法
public class ArraycopyDemo {
    public static void main(String[] args) {
        Beauty[] beauties = new Beauty[5];
        beauties[0] = new Beauty("王昭君", 5, 86.25);
        beauties[1] = new Beauty("赵飞燕", 6, 76.25);
        beauties[2] = new Beauty("陈圆圆", 7, 56.25);
        beauties[3] = new Beauty("杨玉环", 8, 66.25);
        beauties[4] = new Beauty("苏妲己", 9, 96.25);
        Beauty[] newBeauties = new Beauty[beauties.length];
        System.arraycopy(beauties, 0, newBeauties, 0, beauties.length);
        //修改复制后数组元素的属性
        newBeauties[1].setName("洛神甄氏");

        //打印原数组中的内容,观察1号元素的name属性,已经被修改了
        for(Beauty beauty : beauties) {
            System.out.println(beauty);
        }       
    }
}

运行结果:
blob.jpg
内存原理如下:
blob.jpg

得出的结论:
1、当数组为一维数组,且元素为基本类型或String类型时,属于深复制,即原数组与新数组的元素不会相互影响
2、当数组为多维数组,或一维数组中的元素为引用类型时,属于浅复制,原数组与新数组的元素引用指向同一个对象
这里说的影响,是两个数组复制后对应的元素,并不一定是下标对应
String的特殊是因为它的不可变性
多维数组实际上可以理解为一维数组中的每个元素又是一个一维或多维数组的首地址,所以复制时的效果与对象数组的结果是一样的

一、 获取JLayer工具包

1.1 下载JLayer

官网地址
www.javazoom.net
项目地址
http://www.javazoom.net/javalayer/javalayer.html
下载地址
http://www.javazoom.net/javalayer/sources/jlayer1.0.1.tar.gz
需要使用的包
下载地址下载之后,解压之后文件夹中的jl1.0.1.jar文件就是我们需要使用的工具包。

二、将工具包配置到项目中

创建项目时,建议JDK的版本为1.8,jdk9以及以上版本可能会出现播放没有声音的bug。这个bug主要来源于jdk声音播放本身。

2.1 拷贝帮组文件到项目文件夹

将jar包放置到项目中的指定目录中,我们这里先创建一个libs的目录,并且将jar文件拷贝到目录中。如下图1所示

添加文件到项目中
【图1 添加图片到项目】

2.2 配置jar包到构建路径中

在eclipse项目中,选定上一步复制过来的jar文件。右键->弹出菜单中选择Build Path -> Add To Build Path。将jar包设置到配置路劲中,操作如下图2图3所示。

添加文件到构建路径
【图2 添加图片到项目】
添加成功后
【图3 添加图片到项目】

三、添加音乐文件到项目中

在项目中新建文件夹audios,并且准备一个音乐文件放置到文件夹中。如下图4所示。

添加音乐文件
【图4 添加音乐文件】

四、编写代码以及执行播放

新建测试类SimpleMusicPlayer.java,实现音乐播放
实现步骤如下:
1. 创建文件输入流
2. 创建音乐播放器对象
3. 使用音乐播放器对象播放音乐

播放代码如下

package com.xuetang9.xm.simpleplayer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.Player;

/**演示JLayer播放音乐
 * @author 老九.大师兄
 * @date 2019年7月29日 - 下午8:34:04
 * @copyright © xuetang9
 * @address 成都市西部国际金融中心2栋2201
 * @version 1.0
 */
public class SimpleMusicPlayer {
    public static void main(String[] args) {
        try {
            String musicPath = "audios/那英 - 雾里看花.mp3";
            // 创建音乐文件输入流
            FileInputStream inMusic = new FileInputStream(new File(musicPath));
            // 创建音乐播放器
            Player player = new Player(inMusic);
            System.out.println("开始播放:"+musicPath);
            // 播放音乐,音乐播放结束后程序才会结束
            player.play();
        } catch (FileNotFoundException | JavaLayerException e) {
            e.printStackTrace();
        }
    }
}

最后执行该代码即可成功播放音乐。控制台结果如下图5所示,当然音乐就需要小伙伴在自己电脑上播放才能感受咯。
音乐播放结果
【图5 音乐播放结果】

五、总结

整个操作其实非常简单总结如下:
1. 创建项目
2. 准备依赖jar包
3. 配置jar包到构建路径中
4. 准备希望播放的音乐文件
5. 编写测试代码
6. 播放测试

一、背景

大家在初学Java的时候一般都是采用Eclipse或其他IDE环境,中英文混合时的对齐问题想必都或多或少地困扰过大家,比如下面的代码和在Eclipse中的显示效果:
Java字符串格式构建代码:

public String toString() {
        String str = String.format("%-8s%-4d\t%-8s\t%.2f", name, level, getLevelName(), face);
        return str;
    }

blob.jpg
跟我们设想的并不一样。网上有个比较简单的解决方案,就是在%s后添加\t:

public String toString() {
        String str = String.format("%-8s\t%-4d\t%-8s\t%.2f", name, level, getLevelName(), face);
        return str;
    }

效果如下:
blob.jpg
好了,对于没有强迫症的小伙伴,本文结束,大家按照上面的解决方案修改代码即可。

二、使用JNI调用C/C++实现中英文对齐

JNI,即Java Native Interface,Java本地接口。是Java平台提供的调用本地C/C++代码进行互操作的API。

2.1 本次示例所用的代码如下:
/**
 * 后宫佳丽
 * @author 老九学堂·窖头
 *
 */
public class Beauty {
    private static String[] levelNames = {"秀女", "答应", "常在", "贵人", "嫔", "妃", "贵妃", "皇贵妃", "皇后", "皇太后", "太皇太后", "太皇太后还要往后"};
    private String name;
    private int level;
    private String levelName;
    private double face;        //颜值,可以通过图像AI获取

    public Beauty(String name, int level, double face) {
        this.name = name;
        setLevel(level);
        this.levelName = getLevelName();
        this.face = face;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getLevel() {
        return level;
    }
    public void setLevel(int level) {
        if(level < 0 || level > levelNames.length) {
            this.level = 1;
            return;
        }
        this.level = level;
    }
    public String getLevelName() {
        if(level < 0 || level > levelNames.length) {
            return levelNames[0];
        }
        return levelNames[level - 1];
    }
    public void setLevelName(String levelName) {
        this.levelName = levelName;
    }
    public double getFace() {
        return face;
    }
    public void setFace(double face) {
        this.face = face;
    }
}
/**
 * 使用单例模式的打印类
 * @author 窖头
 *
 */
public class Printer {
    private static Printer printer = null;
    private Printer() {}
    /**
     * 调用native方法打印后宫佳丽的信息
     * @param beauty
     */
    public native void printf(Beauty beauty);

    public static Printer getInstance() {
        if(null == printer) {
            printer = new Printer();
        }
        return printer;
    }   
}

下图是我在Eclipse中创建的工程和class:
blob.jpg

2.2 命令行下执行javah命令,得到包含该本地方法声明的头文件(.h文件)

win+r -> cmd,进入工程根目录的bin目录,输入以下指令:

//包名及类名请根据自己的定义进行修改
javah -jni com.xuetang9.kenny.util.Printer

blob.jpg
这里如果出现错误,请检查并重新配置Java的环境变量

获得头文件:com_xuetang9_kenny_util_Printer.h
头文件以包名_方法名的方式命名,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xuetang9_kenny_util_Printer */

#ifndef _Included_com_xuetang9_kenny_util_Printer
#define _Included_com_xuetang9_kenny_util_Printer
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xuetang9_kenny_util_Printer
 * Method:    printf
 * Signature: (Lcom/xuetang9/kenny/entity/Beauty;)V
 */
JNIEXPORT void JNICALL Java_com_xuetang9_kenny_util_Printer_printf
  (JNIEnv *, jobject, jobject);


/** 自定义函数:将Java传来的字符串转换为GB2312以便显示 */
char* jstringToWindows(JNIEnv *, jstring);
/** 自定义函数:将gb2312转换为UTF8/16,以便传回给Java能够正常显示 */
jstring WindowsTojstring(JNIEnv* env, const char * );
//关于为什么使用两个自定义转换函数请参见:http://wiki.xuetang9.com/?p=5270
#ifdef __cplusplus
}
#endif
#endif
2.3 下面根据头文件,书写C++代码,实现本地方法

在头文件旁创建C++源文件:com_xuetang9_kenny_util_Printer.cpp
文件名不变,后缀名修改为cpp,实现代码如下:

#include "com_xuetang9_kenny_util_Printer.h"

#include <stdio.h>
#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <Windows.h>

using namespace std;

JNIEXPORT void JNICALL Java_com_xuetang9_kenny_util_Printer_printf(JNIEnv * env, jobject jobj, jobject jbeauty)
{
    jclass beautyClass = env->GetObjectClass(jbeauty);  //获得Java传来的后宫佳丽对象

    //获得属性句柄(ID)
    jfieldID nameFid =      env->GetFieldID(beautyClass, "name", "Ljava/lang/String;");
    jfieldID levelFid =     env->GetFieldID(beautyClass, "level", "I"); //整型为I,double类型为D
    jfieldID levelNameFid = env->GetFieldID(beautyClass, "levelName", "Ljava/lang/String;");
    jfieldID faceFid =      env->GetFieldID(beautyClass, "face", "D");

    //获得name属性的值
    jstring jNameField = (jstring)env->GetObjectField(jbeauty, nameFid);
    jint jLevelField = (jint)env->GetIntField(jbeauty, levelFid);
    jstring jLevelNameField = (jstring)env->GetObjectField(jbeauty, levelNameFid);
    jdouble jFaceField = env->GetDoubleField(jbeauty, faceFid);

    //const char * cNameField = env->GetStringUTFChars(jNameField, NULL);
    //const char * cLevelNameField = env->GetStringUTFChars(jLevelNameField, NULL);
    //C++中的打印格式控制,左对齐,单独设置每个元素的宽度
    cout.setf(ios::left);
    cout << setw(12) << jstringToWindows(env, jNameField);
    cout << setw(4)  << jLevelField;
    cout << setw(8)  << jstringToWindows(env, jLevelNameField);
    cout << setw(7)  << jFaceField << endl;
    //释放字符串所占的空间
    //env->ReleaseStringUTFChars(jNameField, NULL);
    //env->ReleaseStringUTFChars(jLevelNameField, cLevelNameField);
}

//字符串转换函数,了解做什么的即可
/**
 * 将Java传来的UTF8/16编码转换为C/C++能够正常显示的GB2312编码
 */
char* jstringToWindows( JNIEnv *env, jstring jstr )
{
    int length = (env)->GetStringLength(jstr);
    const jchar* jcstr = (env)->GetStringChars(jstr, 0);
    char* rtn = (char*)malloc(length*2 + 1);
    int size = 0;
    size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn,(length*2+1), NULL, NULL);
    if( size <= 0 )
        return NULL;
    (env)->ReleaseStringChars(jstr, jcstr);
    rtn[size] = 0;
    return rtn;
}

/**
 * 将C/C++中的GB2312编码转换成UTF8/16编码
 */
jstring WindowsTojstring( JNIEnv* env, const char* str )
{
    jstring rtn = 0;
    int slen = strlen(str);
    unsigned short * buffer = 0;
    if( slen == 0 )
    {
        rtn = (env)->NewStringUTF(str );
    }
    else
    {
        int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
        buffer = (unsigned short *)malloc( length*2 + 1 );
        if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )
            rtn = (env)->NewString(  (jchar*)buffer, length );
    }
    if(buffer) free( buffer );
    return rtn;
}
2.4 使用Gcc编译生成共享库dll文件

MinGw64位的下载地址:https://jaist.dl.sourceforge.net/project/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/8.1.0/threads-posix/seh/x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z
blob.jpg

配置好MinGw的环境变量后,键入下面的命令:

g++ -m64 -static-libgcc -static-libstdc++ -I"C:\Program Files\Java\jdk1.8.0_201\include" -I"C:\Program Files\Java\jdk1.8.0_201\include\win32" -shared -o Printer.dll com_xuetang9_kenny_util_Printer.cpp

blob.jpg
1、路径C:\Program Files\Java\jdk1.8.0_201\include和
C:\Program Files\Java\jdk1.8.0_201\include\win32
分别包含了JNI的头文件,<jni.h>和<jni_md.h>,请大家根据自己机器配置的不同,自行修改路径
2、-m64表示生成64位dll库文件

2.5 在Java中调用本地库文件

书写Java测试类:

import java.io.File;
import com.xuetang9.kenny.entity.Beauty;
import com.xuetang9.kenny.util.Printer;

public class TestPrinter {
    public static void main(String[] args) {
        //请大家自行修改成自己机器的路径
        String path = "C:\\Users\\窖头\\eclipse-workspace\\PrintMsgByCpp\\bin\\Printer.dll";
        File file = new File(path);
        //加载本地dll库
        System.load(file.getAbsolutePath());

        Beauty[] beauties = new Beauty[5];
        for(int i = 0; i < beauties.length; i++) {
            beauties[i] = new Beauty();
        }
        beauties[0] = new Beauty("貂蝉1号", 5, 86.25);
        beauties[1] = new Beauty("a赵飞燕b", 6, 76.25);
        beauties[2] = new Beauty("ab西施bc", 7, 56.25);
        beauties[3] = new Beauty("北岸初晴", 8, 66.25);
        beauties[4] = new Beauty("龙a女d", 9, 96.25);

        for(int i = 0; i < beauties.length; i++) {
            //调用本地C++方法打印对象的内容
            Printer.getInstance().printf(beauties[i]);
        }
    }
}

如果直接在Eclipse中运行这个main方法,会抛出异常:java.lang.UnsatisfiedLinkError: %1 不是有效的 Win32 应用程序
反正未来我们开发完成的程序也不可能在Eclipse中执行,所以我们直接在控制台下执行并观察结果:

java com.xuetang9.kenny.TestPrinter

blob.jpg
显示效果非常完美,大功告成!

小伙伴们如果想搞明白C++中的代码含义,或者以后想在混编方面有所发展,可以点击下载JNI参考资料

在JNI调用C实现的本地方法时,我们曾经介绍过直接修改控制台代码页的方式解决中文乱码问题(文章参见:http://wiki.xuetang9.com/?p=5254 ),但是到了C++实现,这个方法又不管用了,折腾了一个下午,终于找到了解决问题的方法,分享如下:

1、相关概念

大家都知道,Java内部采用的是16位Unicode编码来表示字符串,中英文都采用2字节;而JNI内部是使用UTF-8编码来表示字符串,UTF-8编码其实就是Unicode编码的一个变长版本(对应关系参见文章:http://wiki.xuetang9.com/?p=5207 ),一般的ASCII字符占1个字节,中文汉字是3个字节;
C/C++使用的是原始数据,ASCII字符就是一个字节,中文一般是GB2312编码,使用两个字节表示一个中文字符。
明确了概念,操作就比较清楚了。下面根据字符流的方向来分别说明一下:

1-1:从Java 到 C/C++

这种情况下,Java调用的时候使用的是UTF-16编码的字符串,JVM把这个字符串传给JNI,C/C++得到的输入是jstring,这个时候,可以利用JNI提供的两种函数,一个是GetStringUTFChars,这个函数将得到一个UTF-8编码的字符串;另一个是 GetStringChars这个将得到UTF-16编码的字符串。无论哪个函数,得到的字符串如果含有中文,都需要进一步转化成GB2312的编码。示意图如下:
blob.jpg

1-2:从C/C++ 到 Java

从JNI返回给Java的字符串,C/C++首先就会负责把字符串转换为UTF-8或UTF-16的格式,然后通过NewStringUTF()或NewString()方法将字符串封装成jstring,返回给Java就可以了:
blob.jpg

如果字符串中不含中文字符,只是标准ASCII码,使用GetStringUTFChars()/NewStringUTF()方法就可以搞定了,因为这个情况下,UTF-8编码和ASCII编码是一致的,不需要转换。

但是如果字符串中存在中文字符,那么就必须在C/C++程序中进行转码操作。这里要说明一下:Linux和Win32都支持wchar,这个实际上就是宽度为16位的Unicode编码UTF-16。所以,如果我们的C/C++程序中完全使用wchar类型,理论上就不需要这种硬转换了。但是实际上,大家在写程序的时候不可能完全用wchar来取代char,所以就目前大多数应用而言,转换仍然是必须的。

2、转换方法

需要包含的头文件:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <Windows.h>

使用wide char 类型来做转换:

/**
 * 将Java传来的UTF8/16编码转换为C/C++能够正常显示的GB2312编码
 */
char* jstringToWindows( JNIEnv *env, jstring jstr )
{
    int length = (env)->GetStringLength(jstr);
    const jchar* jcstr = (env)->GetStringChars(jstr, 0);
    char* rtn = (char*)malloc(length*2 + 1);
    int size = 0;
    size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn,(length*2+1), NULL, NULL);
    if( size <= 0 )
        return NULL;
    (env)->ReleaseStringChars(jstr, jcstr);
    rtn[size] = 0;
    return rtn;
}
/**
 * 将C/C++中的GB2312编码转换成UTF8/16编码
 */
jstring WindowsTojstring( JNIEnv* env, const char* str )
{
    jstring rtn = 0;
    int slen = strlen(str);
    unsigned short * buffer = 0;
    if( slen == 0 )
    {
        rtn = (env)->NewStringUTF(str );
    }
    else
    {
        int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
        buffer = (unsigned short *)malloc( length*2 + 1 );
        if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )
            rtn = (env)->NewString(  (jchar*)buffer, length );
    }
    if(buffer) free( buffer );
    return rtn;
}

关于codeblocks无法使用std::to_string()方法解决方案

当我们在使用codeblocks编写C++程序的时候,经常会有一个需求,其他类型的数据转换成字符串类型,在C++ 11提供了to_string这个方法来进行转换,但是在使用codeblocks它不怎么友好,总是会看到编译器提示错误to_string was not declared,此时感觉很郁闷,不过没关系,下面总结一下怎么解决这个问题。
第一步首先我们需要设置codeblocks拥有对c++11的支持,依次选择菜单Settings->Compiler settings,然后按如图红框地方进行设置
blob.jpg
一般来说执行了这一步就已经可以正常编译通过了,如果此时仍然编译不过,那就是我们安装的的gcc编译器,提供的头文件没有这些方法的提供,所以我们需要替换一些头文件来完成,替换文件请下载附件:mingw-to-string-gcc
将下载下来的附件进行解压缩然后将文件复制到对应的目录即可
首先复制include目录下面的头文件到你的 MinGw\include目录替换掉原来的文件,比如我的MinGW就在codeblocks安装目录下,如下图所示
blob.jpg
然后复制os_defines.h到MinGW安装目录下的
lib\gcc\mingw32\版本号文件夹\include\c++\mingw32\bits的目录并替换掉原来的文件,比如在我的电脑中,可以看到如下图的目录结构
blob.jpg
到此操作完成,下面再来进行编译就能够解决其他问题

在程序开发中我们统一的编码是 UTF-8,这一点很重要,但是总还是会遇到许多乱码问题,乱码的原因是多方面的,这里暂不过多总结。这里只说明在window下使用控制台执行一些自己编写的程序,或者是进行交互式编程的时候会经常遇到乱码问题,这是因为Window cmd的默认编码是GBK。与程序采用的 UTF-8 不一致造成的中文及特殊字符乱码。
blob.jpg

第一种方式:临时性修改控制台的显示编码

使用 chcp 命令,例如 chcp 65001 ,这回将当前代码页变为 utf-8编码,不过这种方式在关闭 cmd 之后会自动失效。
blob.jpg
常用的编码及对应的码值(十进制):
blob.jpg

第二种方式:永久修改

永久性修改就是通过修改注册注册表达到。
打开注册表方法:win+r -> regedit
定位到:HKEY_CURRENT_USER\Console\%SystemRoot%_system32_cmd.exe
需要注意的是,默认是没有这个键值的。只有在标题栏右键修改过设置才会出现。删除这个项,就会使用console的默认设置。 如果要修改,可以在cmd的标题栏右键设置,随便修改一个属性,然后刷新下注册表就可以了。
blob.jpg

右侧如果没有CodePage,则右键新建一个DWORD值,直接将其修改为65001即可(十进制的65001)

同理也可以修改:powershell 的默认编码。如果因为权限问题无法修改的,可以右键左边选中的红框,选择权限,赋给自己完全控制的权限就可以了!

1、下载软件压缩包文件,首先点击“astah-professional-8_0_0-d641d4-jre-64bit-setup.exe”安装原版程序
32位:https://pan.baidu.com/s/19NBG2KQ-UubaOwuVtql_FA 分享码:scd7
64位:https://pan.baidu.com/s/1cSIoJOUwGPrhFODRNWPfeA 分享码:fqbx
blob.jpg

2、阅读并同意软件安装协议
blob.jpg

3、设置软件安装路径
blob.jpg

4、选择安装组件
blob.jpg

5、选择附加任务,建议创建桌面快捷方式
blob.jpg

6、核对安装信息,确认无误后即可点击【Install】按钮继续
blob.jpg

7、等待程序安装完毕,先不要启动软件,直接点击【Finish】按钮退出向导
blob.jpg

8、将破解补丁复制到软件根目录下,选择替换操作
默认路径为:C:\Program Files\astah-professional
blob.jpg

9、完成上述操作,打开程序享受UML开发的快乐吧~
blob.jpg

HashMap中hash方法的源码如下:

//jdk1.7版本
static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
//Java 8中的散列值优化方法
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

上面这段代码叫做“扰动函数”,在Java8中进行了简化,只做一次16位位移或混合,而不是四次,原理不变。下面以Java8的源码为例解释:

大家都知道上面代码里的key.hashCode()方法调用的是key键类型自带的哈希方法,返回整型散列值。理论上散列值是一个int类型,如果直接拿散列值作为下标访问HashMap主数组的话,考虑到2进制32位带符号的整型范围从-2147483648到2147483648,前后加起来大概40亿的映射空间。只要哈希方法映射地比较均匀松散,一般应用是很难出现碰撞的。

但问题是一个40亿长度的数组,内存是放不下的。各位想,HashMap的初始容量大小才16!所以这个散列值并不能直接拿来用,用之前还要先做对数组的长度取模运算,得到的余数再拿来访问数组下标。

jdk源码中的模运算是在indexFor()方法中完成的,indexFor()方法的代码很简单,就是把散列值和数组长度做一个“与”操作:

static int indexFor(int h, int length){
    return h & (length - 1);
}
....
bucketIndex = indexFor(hash, table.length);

这里顺便讲一下为什么HashMap的数组长度要取2的整次幂。因为这样(数组长度-1)正好相当于一个“低位掩码”。“与操作”的结果就是散列值的高位全部归零,只保留低位值,用来做数组的下标。以初始长度16为例:16-1=15,2进制表示是 00000000 00000000 00001111。和某散列值做“与”操作的结果如下:

    10100101 11000100 00100101  //某散列值)
&   00000000 00000000 00001111
----------------------------------------------------------------
    00000000 00000000 00000101  //高位全部归零,只保留末四位

我们发现,最终的结果就是截取了最低的四位值。
这时问题就来了,就算我们的散列值分布再松散,要是只取最后几位的话,碰撞会非常严重。更要命的是,如果散列本身做的不好,分布上成等差数列的漏洞,恰好使最后几个低位呈现规律性重复,这会无比蛋疼。
这时候“扰动方法”的价值就体现出来了,请看下图:
blob.jpg
右移16位,刚好是32位的一半,自己的高半区和低半区做异或(如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0),就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。
而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留了下来

最后我们来看一下Peter Lawley的一篇专栏文章《An introducion to optimising a hashing stretegy》( https://www.javacodegeeks.com/2015/09/an-introduction-to-optimising-a-hashing-strategy.html )里的一个实验:他选取了352个字符串,在散列值完全没有冲突的前提下,对它们做低位掩码,取数组下标:
blob.jpg
结果显示,当HashMap数组长度为512的时候,也就是用掩码取低9位的时候,在没有扰动方法的情况下,发生了103次碰撞,接近30%。而在使用了扰动方法后,只有92次碰撞,碰撞减少了近10%。扰动方法的确发挥了不错的功效。
Java7中的扰动做了四次,而到了Java8,觉得做一次就够了,多了边际效用也不大,这就是所谓的为了效率考虑就改成了1次扰动,相比较而言减少了过多的位运算,是一种折中的设计。

一、使用数组的好处

1,节约变量名
2,操作方便,指对大量同类型的数据操作时

二、数组的复制

1,虚假的复制

int[] array1 = {12,23,45,22,42,32};
int[] array2 = {32,42,45,21,11,65,35,66};
System.out.println("直接复制前:");
System.out.println(array1);
System.out.println(array2);
array1 = array2;
System.out.println("直接复制后:");
System.out.println(array1);
System.out.println(array2);
System.out.println(Arrays.toString(array1));
System.out.println(Arrays.toString(array2));

结果:

直接复制前:
[I@1e643faf
[I@6e8dacdf
直接复制后:
[I@6e8dacdf
[I@6e8dacdf
[32, 42, 45, 21, 11, 65, 35, 66]
[32, 42, 45, 21, 11, 65, 35, 66]

由此可以轻松看出这样直接 = 只是将array2的地址给了array1,并没有实现我们真正的目的。

2,真实的复制
因为java里数组定义时就已经确定了长度,所以想要复制一个长度不等的数组,就只能新建一个数组,再将array1指向这个新数组。
这样就有几种方法了:
1,直接用new新建一个,然后用for循环复制赋值,就能形成复制。
array1 = new int[array2.length];
2,用Arrays.copeof(),java提供的复制功能,

System.out.println("Arrays复制后:");
array1 = Arrays.copyOf(array2,array2.length);
System.out.println(array1);
System.out.println(Arrays.toString(array1));
Arrays复制后:
[I@7a79be86
[33, 42, 45, 21, 11, 65, 35, 66]

可以看到,array1的地址变成了一个新的地址

3,使用方案2的底层实现,效率高

System.arraycopy(src,srcPos,dest,destPos,length);

src – 源数组。
srcPos – 源数组中的起始位置。
dest – 目标数组。
destPos – 目的地数据中的起始位置。
length – 要复制的数组元素的数量。
要注意,这样并没有解决数组长度的问题,需要和
array1 = new int[array2.length];配合使用。
但这样比起用for循环赋值要快的多。

java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入。

下面是创建 Scanner 对象的基本语法:

Scanner scanner = new Scanner(System.in);

接下来我们演示一个最简单的字符串输入,并通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串:

//创建Scanner对象
Scanner scanner = new Scanner(System.in);

System.out.print("使用next()方法接收用户的输入:");
String str = scanner.next();
System.out.println("用户输入的字符串为:" + str);

//关闭Scanner对象
scanner.close();

输出结果:
blob.jpg
输入的三个字符串神仙、妖怪、谢谢 使用空格分隔,我们发现只输出了第一个词:神仙
下面使用nextLine()方法试试:

//创建Scanner对象
Scanner scanner = new Scanner(System.in);

System.out.println("使用nextLine()方法接收用户的输入:");
String str = scanner.nextLine();
System.out.println("用户输入的字符串为:" + str);

//关闭Scanner对象
scanner.close();

输出结果变成了:
blob.jpg

下面简单总结下next() 与 nextLine() 区别
next():
1、如果用户只输入一个回车,程序会等待继续输入,要读取到有效字符后才可以结束输入;
2、next()方法会自动去除有效字符前的空白(空白符/回车符等)
3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
next() 不能得到带有空格的字符串。

nextLine():
1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
2、可以获得空白

如果要输入 int 或 float 类型的数据,在 Scanner 类中也有支持,但是在输入之前最好先使用 hasNextXxx() 方法进行验证,再使用 nextXxx() 来读取:

Scanner scanner = new Scanner(System.in);
//接收从键盘输入的数据:
int intNum = 0;
float floatNum = 0.0f;
System.out.print("接收用户输入的整数:");
if (scanner.hasNextInt()) {// 判断输入的是否是整数
    intNum = scanner.nextInt();// 接收整数
    System.out.println("用户输入的整数是:" + intNum);
} else {
    // 输入错误的信息
    System.out.println("输入的不是整数!");
}
System.out.print("接收用户输入的小数:");
if (scanner.hasNextFloat()) {// 判断输入的是否是小数
    floatNum = scanner.nextFloat();// 接收小数
    System.out.println("用户输入的小数是:" + floatNum);
} else {
    // 输入错误的信息
    System.out.println("输入的不是小数!");
}
scanner.close();

如果配上while循环,还可以实现循环验证直到输入正确的功能:

Scanner input = new Scanner(System.in);
int num;
System.out.println("请输入一个整型数字:");
while(!input.hasNextInt()) {
    System.out.println("输入的不是整型,请重新输入:");
    //hasNextInt判断不是整型后,缓存中还是存在用户输入的内容
    //如果再次使用hasNext..判断,仍然会得出不是整型的结果
    //所以在这里单独使用input.next(),以便将缓存清除掉
    input.next();
}
num = input.nextInt();
System.out.println("输入的整型数字是:" + num);