java1-基本数据类型/包装类-数组Array及Arrays-集合Collection/Map-大数值-atomic类-lock类-BlockingQueue类-Collections.synchronized方法
1,基本数据类型
1-1,数值型
- 整型:byte(8bit)、short(16bit)、int(32bit)、long(64bit)
- 浮点型:float(32bit)、double(64bit)
1-2,字符型:char
-
字符型转int类型:
int num = (int)var;
-
int类型转char类型:
char var = (char)num;
-
可以将字符直接赋予int类型的变量,或者将整数直接赋值给char类型:因为char类型在java中显示为字符,储存为数字。
int num = '中'; char var = 20013;
-
char chr = '2'+2; System.out.println(chr); //输出结果为4,是因为'2'的ascii码为50,50+2=52,'4'的ascii码为52,所以显示为'4'。
1-3,布尔型:boolean
-
有两个常量值,在内存中占1bit,不可以使用0或非0的整数替代true和false
public class TestVar06{ public static void main(String[] args){ boolean flag1 = true; System.out.println(flag1); boolean flag2 = false; System.out.println(flag2); boolean flag3 = 5==9; System.out.println(flag3); } }
1-4,布尔型:boolean
1-5,对应包装类
- 包装类对应关系:
基本数据类型 包装类型
byte java.lang.Byte(父类Number)
short java.lang.Short(父类Number)
int java.lang.Integer(父类Number)
long java.lang.Long(父类Number)
float java.lang.Float(父类Number)
double java.lang.Double(父类Number)
boolean java.lang.Boolean(父类Object)
char java.lang.Character(父类Object)
- 在 Java 5 中,引入了自动装箱和自动拆箱功能(boxing/unboxing),Java 可以根据上下文,自动进行转换,极大地简化了相关编程。
- 自动装箱、拆箱发生在编译阶段,javac自动把装箱转换为
Integer.valueOf()
,把拆箱替换为Integer.intValue()
。
- 自动装箱、拆箱发生在编译阶段,javac自动把装箱转换为
- 关于 Integer 的值缓存,这涉及 Java 5 中另一个改进。构建 Integer 对象的传统方式是直接调用构造器,直接 new 一个对象。但是根据实践,我们发现大部分数据操作都是集中在有限的、较小的数值范围,因而,在 Java 5 中新增了静态工厂方法 valueOf,在调用它的时候会利用一个缓存机制,带来了明显的性能改进。按照 Javadoc,这个值默认缓存是 -128 到 127 之间。
- 使用 valueOf 才能用到缓存,而 new 对象则不会。
- 缓存机制并不是只有 Integer 才有,同样存在于其他的一些包装类,比如:
- Boolean,缓存了 true/false 对应实例,确切说,只会返回两个常量实例 Boolean.TRUE/FALSE。
- Short,同样是缓存了 -128 到 127 之间的数值。
- Byte,数值有限,所以全部都被缓存。
- Character,缓存范围’\u0000’ 到 ‘\u007F’。
- Integer 缓存上限值实际是可以根据需要调整的,JVM 提供了参数设置:
-XX:AutoBoxCacheMax=N
- 原则上,建议避免无意中的装箱、拆箱行为,尤其是在性能敏感的场合,创建 10 万个 Java 对象和 10 万个整数的开销可不是一个数量级的,不管是内存使用还是处理速度,光是对象头的空间占用就已经是数量级的差距了。我们其实可以把这个观点扩展开,使用原始数据类型、数组甚至本地代码实现等,在性能极度敏感的场景往往具有比较大的优势,用其替换掉包装类、动态数组(如 ArrayList)等可以作为性能优化的备选项。
- 原始数据类型和 Java 泛型并不能配合使用,也就是Primitive Types 和Generic 不能混用,于是JAVA就设计了auto-boxing/unboxing机制,实际上就是primitive value 与 object之间的隐式转换机制,否则要是没有这个机制,开发者就必须每次手动显示转换,那多麻烦是不是?但是primitive value 与 object各自有各自的优势,primitive value在内存中存的是值,所以找到primitive value的内存位置,就可以获得值;不像object存的是reference,找到object的内存位置,还要根据reference找下一个内存空间,要产生更多的IO,所以计算性能比primitive value差,但是object具备generic的能力,更抽象,解决业务问题编程效率高。于是JAVA设计者的初衷估计是这样的:如果开发者要做计算,就应该使用primitive value;如果开发者要处理业务问题,就应该使用object,采用Generic机制;反正JAVA有auto-boxing/unboxing机制,对开发者来讲也不需要注意什么。然后为了弥补object计算能力的不足,还设计了static valueOf()方法提供缓存机制,算是一个弥补。
2,引用数据类型:
- 类:class
- 接口:interface
- 数组
3,基本数据类型转换:
-
在赋值运算或者算术运算的时候,要求数据类型一致,就要进行数据类型的转换。
-
类型转换分为:自动转换、强制转换
-
类型级别:
-
由低到高:byte,short,char-->int-->long-->float-->double
-
当一个表达式中有多种数据类型时,所有变量都会转换为级别最高的数据类型再进行计算
-
进行运算时:
- 左侧优先级与右侧相同:直接运算
- 左侧优先级低于右侧:右侧强行转换
- 左侧优先级高于右侧:右侧自动转换
public class TestVar07{ public static void main(String[] args){ double d = 6; //自动转换 System.out.println(d); //int e = 6.5; //自动转换报错 //System.out.println(e); int e = (int)6.5; //强制转换,该转换直接舍弃小数点后的数 System.out.println(e); /* ============================================================ 在同一个表达式中,如果有多个数据类型时: 运算:整数、浮点数、字符型都可以参与运算,布尔型不可 ============================================================ */ //double d2 = 12+1294L+8.5f+3.81+'a'+true; double d2 = 12+1294L+8.5f+3.81+'a'; System.out.println(d2); int i2 = (int) (12+1294L+8.5f+3.81+'a'); System.out.println(i2); } }
-
特殊情况:对于byte,short,char类型的变量,只要在他们表达的数值范围内,赋值的时候就不需要强行转换了,直接赋值/运算即可
public class TestVar08{ public static void main(String[] args){ byte b = 12; System.out.println(b); } } /* 默认12为int类型,优先级高于byte(-128,127),但是将12直接赋值给b并不会报错。 */
-
4,键盘输入及final关键字
-
final: 一个变量被final修饰,就成为了一个常量,值不可再变;这个常量就是符号常量(字符常量,区别为字面常量);一般字符常量的名字全部大写。
import java.util.Scanner; public class TestVar09{ public static void main(String[] args){ //提取变量 final double PI = 3.1415926; //扫描器 System.out.print("请输入半径:"); Scanner sc = new Scanner(System.in); int r = sc.nextInt(); double c = 2 * PI * r; System.out.println("周长为:"+c); double s = PI * r * r; System.out.println("面积为:"+s); //PI = 9; //报错 } }
-
输入:Scanner 与字符串:String(charAt转为字符型)
import java.util.Scanner; public class TestVar10{ public static void main(String[] args){ Scanner sc = new Scanner(System.in); System.out.print("请输入年龄:"); int age = sc.nextInt(); System.out.print("请输入身高:"); double height = sc.nextDouble(); System.out.print("请输入姓名:"); String name = sc.next(); System.out.print("请输入性别:"); char sex = sc.next().charAt(0); System.out.println("性别为:"+sex); } }
5,数组
5-1,数组的基本概念及操作(声明、创建/初始化、赋值、初始化)
-
数组用来存储数据,可以将相同类型的若干数据组织起来,将这个若干数据集合称为数组。
-
数组的使用:
-
声明(定义数组)
int[] arr; int arr[];
-
创建
arr = new int[4]; //声明和创建同时进行 int[] arr = new int[4];
-
赋值
arr[0] = 1; arr[1] = 2; ...
-
使用
System.out.println(arr[0]) System.out.println(arr.length) //获取长度 System.out.println(arr) //首地址
-
-
数组的初始化
//静态初始化,不可分开写 int[] arr = {12,34,56}; int[] arr = new int[]{12,34,56}; //动态初始化 int[] arr = new int[3]; //这一步可以分开 int[] arr; arr = new int[3;] arr[0] = 12; arr[1] = 34; arr[2] = 56; //默认初始化 int[] arr = new int[3]; //数字类型的数组都是对应的0,char为'\u0000',boolean为false //二维初始化 int[][] arr = {{-1,0}, {1,0}, {0,-1}, {0,1}};
-
java数组的特点:
- 长度是确定的,一旦被创建,长度不可改变
- 元素必须是相同类型,不允许出现混合类型
- 数组类型可以是任意数据类型:基本类型,引用类型
- 数组变量属于引用类型,数组也是对象,在堆中存储
5-2,数组的增删改查
-
增
//定长数组 for(int i=arr.length-1;i>=(index+1);i--){ arr[i] = arr[i-1]; } arr[index] = target; //元素不丢失 int[] arr2 = new int[arr.length+1]; for (int i=0;i<arr.length;i++){ if(i<index){ arr2[i] = arr[i]; }else if(i>index){ arr2[i] = arr[i-1]; } } arr2[index] = target;
-
删
-
删除指定位置的元素
for(int i=index;i<arr.length;i++){ arr[i] = arr[i+1]; } arr[arr.length-1] = 0;
-
删除指定的元素
先由元素获取索引 再删除指定位置的元素
-
-
改
arr[index] = target;
-
查
-
由索引获取元素
num = arr[索引]
-
由元素获取索引
//遍历; int index = -1; //初始化为数组不包含的索引,防止有的元素不在数组中 for(int i=0;i<arr.length;i++){ if(arr[i]==num){ index = i; break; } }
-
5-3,Arrays工具类
-
常用方法
Arrays.toString(arr) //将数组转为字符串 Arrays.sort(arr) //对数组进行升序排列 Arrays.sort(arr, Colletions.reverseOrder()) //反向排序 Arrays.sort(arr, (a,b)->(b-a)) //使用匿名类进行反向排序 Arrays.binarySearch(arr, key) //使用二分查找对排序后的数组寻找key的索引,数组在调用前必须排序好的,如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。(来自菜鸟教程:https://www.runoob.com/java/java-array.html) Arrays.copyOf(arr, length) //复制length长度的arr为一个新数组 Arrays.copyOfRange(arr, i, j) //赋值arr从i到j的范围为一个新数组,顾头不顾尾 Arrays.equals(arr1, arr2) //比较两个数组的值是否相同 Arrays.fill(arr, i, index1, index2) //使用i将arr填充
5-4,数组复制
System.arraycopy(src, srcPos, dest, destPos, length)
//src 源数组
//srcPos 源数组起始位置
//dest 目标数组
//destPos 目标数组起始位置
//length 要复制的长度
5-5,二维数组的定义,遍历,初始化
public class Test09{
public static void main(String[] args){
//定义
int[][] arr = new int[3][];
arr[0] = new int[]{1,2,3,4};
arr[1] = new int[]{2,3,4,5};
arr[2] = new int[]{3,4,5,6};
//遍历,内层外层都可以用普通和增强遍历
for(int[] arrMiddle:arr){
System.out.println("======");
for(int num:arrMiddle){
System.out.println(num);
}
}
}
}
-
初始化
//静态初始化,除了用new关键字产生数组以外,可以直接在定义数组的同时为数组元素分配空间并赋值 int[][] arr = {{1,2},{2,3},{3,4}}; int[][] arr = new int[][]{{1,2},{2,3},{3,4}}; //动态初始化 int[][] arr = new int[3][]; //本质上定义一个一维数组,长度为3,每个位置可以放入一个数组 arr[0] = {1,2,3,4}; arr[1] = {2,3,4,5}; arr[2] = {3,4,5,6}; int[][] arr = new int[3][2];//本质上定义一个一维数组,长度为3,每个位置可以放入一个长度为2的数组 //默认初始化 int[][] arr = new int[3][2]; //数字类型的数组都是对应的0,char为'\u0000',boolean为false
5-6,数组的使用例子
例子1
import java.util.Scanner;
public class Test02{
public static void main(String[] args){
int[] grades = new int[10];
int sum = 0;
Scanner sc = new Scanner(System.in);
for (int i=0; i<10; i++){
System.out.print("请输入第"+i+"个学生的成绩:");
grades[i] = sc.nextInt();
sum += grades[i];
}
System.out.println("学生的总成绩是:"+sum);
System.out.println("学生的平均成绩是:"+sum/grades.length);
System.out.println("各学生的成绩是:");
System.out.println(grades); //这样打印出来的只是数组的首地址
for(int grade:grades){
System.out.println(grade);
}
while(true){
System.out.println("请输入你要查询的学生的序号,退出请输出Q");
if (!sc.hasNextInt()){
//此时输入为字符
if(sc.next().charAt(0)=='Q'){
//退出
break;
}else{
System.out.println("违法输入!!!");
}
}else{
int order = sc.nextInt() - 1;
System.out.println("该生成绩为:"+grades[order]);
}
}
}
}
例子2
public class Test03{
public static void main(String[] args){
int[] arr = {1,2,3,4,5,6,7,8,9};
int minNumber = Test03.getMinNumber(arr);
int maxNumber = Test03.getMaxNumber(arr);
System.out.println("最小值为:"+minNumber);
System.out.println("最大值为:"+maxNumber);
}
public static int getMaxNumber(int[] arr){
int maxNumber = arr[0];
for (int num:arr){
if(num>maxNumber){
maxNumber = num;
}
}
return maxNumber;
}
public static int getMinNumber(int[] arr){
int minNumber = arr[0];
for (int num:arr){
if(num<minNumber){
minNumber = num;
}
}
return minNumber;
}
}
6,Collection及Map
- 继承关系
- Collection <- List <- ArrayList,LinkedList //最大特点,存储的元素有序(插入时的顺序)且可重复
- Collection <- Set <- HashSet //最大特点,存储的元素无序且不可重复
- Collection <- Queue <- ArrayDeque,LinkedList //最大特点,存储的元素有序(特定规则下的顺序,如优先级队列:PriorityQueue)且可重复
- Map <- HashTable //Map的最大特点,键值对的映射
- Map <- HashMap <- LinkedHashMap
- Collection接口定义有toArray,可以将List,Set,Queue转换为数组
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> test = new ArrayList<>();
for(int i=0; i<10; i++){
test.add(i);
}
Integer[] test1 = new Integer[test.size()];
test.toArray(test1);
System.out.println(test1[0]);
System.out.println(test1[0].getClass());
Object[] test2 = test.toArray();
System.out.println(test2[0]);
System.out.println(test2[0].getClass());
}
}
7,大数值
- 如果基本的整数和浮点数精度不能够满足需求,那么可以使用
java.math
包中的两个很有用的类:BigInteger
和BigDecimal
,这两个类可以处理包含任意长度数字序列的数值。BigInteger
类实现了任意精度的整数运算BigDecimal
类实现了任意精度的浮点数运算。
- 使用静态
valueOf
方法可以将普通的数值转化为大数值public class test3 { public static void main(String[] args) throws IOException { BigInteger num1 = BigInteger.valueOf(123); System.out.println(num1.getClass()); } }
- 不能使用人们熟悉的算术运算符(如:
+
和*
)处理大数值。而需要使用大数值类中的add
和multiply
方法//大整数 public class test3 { public static void main(String[] args) throws IOException { BigInteger num1 = BigInteger.valueOf((long)1e18); BigInteger num2 = BigInteger.valueOf((long)1e18); System.out.println(num1.add(num2)); System.out.println((long)1e18+(long)1e18); System.out.println(num1.subtract(num2)); System.out.println((long)1e18-(long)1e18); System.out.println(num1.multiply(num2)); System.out.println((long)1e18*(long)1e18); System.out.println(num1.multiply(num2).divide(BigInteger.TWO)); System.out.println((long)1e18*(long)1e18/2); System.out.println(num1.multiply(num2).mod(BigInteger.TWO)); System.out.println((long)1e18*(long)1e18%2); System.out.println(num1.compareTo(num2)); System.out.println(num2.compareTo(num1)); System.out.println(num1.compareTo(num1)); } }
//大实数 public class test3 { public static void main(String[] args) throws IOException { BigDecimal num1 = BigDecimal.valueOf(3.3333333333); BigDecimal num2 = BigDecimal.valueOf(2.222222222); System.out.println(num1.multiply(num2).multiply(num1).multiply(num1)); System.out.println(num1.divide(num2, RoundingMode.HALF_UP)); //除法需要制定舍入方式 double num3 = 3.3333333333; double num4 = 2.222222222; System.out.println(num3*num4*num3*num3); System.out.println(num3/num4); } }
8,线程安全基础类型-atomic类
- 原始数据类型的变量,要使用并发相关手段,才能保证线程安全;如果有线程安全的计算需要,建议考虑使用类似 AtomicInteger、AtomicLong 这样的线程安全基础类型。
- 在
java1.5
加入了Atomic
这个帮助类型,位于java.util.concurrent.atomic
。这里的atomic是线程安全的基本数据类型。 - atomic系列包含以下数据类型
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicIntegerFieldUpdater
AtomicLong
AtomicLongArray
AtomicLongFieldUpdater
AtomicMarkableReference
AtomicReference
AtomicReferenceArray
AtomicReferenceFieldUpdater
AtomicStampedReference
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder
- Java线程安全基础类型
- AtomicInteger如何保证线程安全?
- 用volatile关键字修饰value字段:AtomicInteger用value字段来存储数据值,volatile关键字保证了value字段对各个线程的可见性。各线程读取value字段时,会先从主内存把数据同步到工作内存,这样保证可见性。
- Unsafe实现操作原子性,用户在使用时无需额外的同步操作:如AtomicInteger提供了自增方法getAndIncrement,其内部实现是由Unsafe类的compareAndSetInt方法来保证的。
- compareAndSetInt方法是一个CAS操作,用native关键字修饰。原理:先比较内存中的值与expected是否一致,一致的前提下才赋予新的值x,此时返回true,否则返回false。
- 总之,AtomicInteger的线程安全由两点保证:
- volatile关键字修饰value字段,保证value字段对各个线程的可见性;
- CAS操作保证了多线程环境下对数据修改的安全性。
- AtomicLong/AtomicBoolean等均为相同的原理。
- 例子
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class TestAtomicInteger {
public static void main(String[] args) {
TestAtomicInteger test = new TestAtomicInteger();
test.test();
}
private static int THREAD_COUNTER = 10;
private CountDownLatch countDownLatcher = new CountDownLatch(THREAD_COUNTER);
private Integer a = Integer.valueOf(0);
private AtomicInteger atomicA = new AtomicInteger(0);
private class CounterRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
// Integer
a++;
// AtomicInteger
atomicA.getAndIncrement();
}
countDownLatcher.countDown();
}
};
private void test() {
CounterRunnable r = new CounterRunnable();
for (int i = 0; i < THREAD_COUNTER; i++) {
Thread thread = new Thread(r);
thread.start();
}
try {
countDownLatcher.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a: " + a);
System.out.println("atomicA: " + atomicA);
}
}
9,Lock类
10,BlockingQueue类
- BlockingQueue阻塞队列体系继承体系
- https://www.cnblogs.com/tjudzj/p/4454490.html
- 在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。
- BlockingQueue的两个常见阻塞场景:
- 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
- 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
- 这也是我们在多线程环境下,为什么需要BlockingQueue的原因。作为BlockingQueue的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。
- 其实BlockingQueue阻塞队列体系的继承(实现)层次比较简单,我们只要需要先学习一下BlockingQueue中的方法:
public interface BlockingQueue<E> extends Queue<E> { void put(E e) throws InterruptedException;--插入一个元素,满了就等待 E take() throws InterruptedException;--弹出队首元素,空了就等待 boolean add(E e);--往队列中插入一个对象,队列满了会抛异常,满了不会等待 boolean offer(E e);--同上,区别是队列满了会返回false boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;--规定时间内插入元素 E poll(long timeout, TimeUnit unit) throws InterruptedException;--规定时间内弹出队首,空了不会等待 int remainingCapacity();--队列剩余大小 boolean remove(Object o);--删除一个equals o的对象 public boolean contains(Object o);--是否包含o int drainTo(Collection<? super E> c);--把队列迁移到另外一个collection结构中 int drainTo(Collection<? super E> c, int maxElements);--迁移,有个最大迁移数量 }
- 下面去看一下具体的几个实现类。
- ArrayBlockingQueue--声明时就确定大小的队列,fifo方式。(方法基本和接口一致,没有特别要说明的内容)
- LinkedBlockingQueue--链表实现的queue-remove效率会高一些。作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
- PriorityBlockingQueue--优先级队列,PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现时,内部控制线程同步的锁采用的是公平锁。
- SynchronousQueue--阻塞队列,必须拿走一个才能放进来一个,也就是最多只有一个~
- DelayQuque--就是放进去的内容,延迟时间到了后才可以获得,DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
- LinkedBlockDeque--双端队列 :offerFirst/offerLast,pollFirst/pollLast
- LinkedTransferQueue--类似LinkedUnBlockedQueue,其实就是transfer方法有人再等待队列内容就直接给他这个元素,没人在等就放在队列里面。也就是效率会更高。
11,Collections.synchronized方法
- Collection/Map的集合类都不是线程安全的,但是并不代表这些集合完全不能支持并发编程的场景。在 Collections 工具类中,提供了一系列的 synchronized 方法,我们完全可以利用类似方法来实现基本的线程安全集合:
List list = Collections.synchronizedList(new ArrayList());
- 它的实现,基本就是将每个基本方法,比如 get、set、add 之类,都通过 synchronized 添加基本的同步支持,非常简单粗暴,但也非常实用。注意这些方法创建的线程安全集合,都符合迭代时 fail-fast 行为,当发生意外的并发修改时,尽早抛出 ConcurrentModificationException 异常,以避免不可预计的行为。
N,不同类型的变量之间的相互转换
num + "" int->String
Interge.parseInt(String) String->int
Integer.valueOf(String) valueOf(String)方法会返回Integer类的对象,而parseInt(String)方法返回原始的int值。
char - '0' char->int(也可隐式转换)
(int)char
str.toCharArray() String->char[]
行动是治愈恐惧的良药,而犹豫拖延将不断滋养恐惧。